diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
deleted file mode 100644
index 7fb48ef20..000000000
--- a/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,93 +0,0 @@
-# Code of Conduct
-
-## Our Pledge
-
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, gender identity and expression, level of
-experience, education, socio-economic status, nationality, personal appearance,
-race, religion, or sexual identity and orientation.
-
-## Our Standards
-
-Examples of behavior that contributes to creating a positive environment
-include:
-
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-## Our Responsibilities
-
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
-
-Project maintainers have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, or to ban temporarily or permanently any
-contributor for other behaviors that they deem inappropriate, threatening,
-offensive, or harmful.
-
-## Scope
-
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by project maintainers.
-
-This Code of Conduct also applies outside the project spaces when the Project
-Steward has a reasonable belief that an individual's behavior may have a
-negative impact on the project or its community.
-
-## Conflict Resolution
-
-We do not believe that all conflict is bad; healthy debate and disagreement
-often yield positive results. However, it is never okay to be disrespectful or
-to engage in behavior that violates the project’s code of conduct.
-
-If you see someone violating the code of conduct, you are encouraged to address
-the behavior directly with those involved. Many issues can be resolved quickly
-and easily, and this gives people more control over the outcome of their
-dispute. If you are unable to resolve the matter for any reason, or if the
-behavior is threatening or harassing, report it. We are dedicated to providing
-an environment where participants feel welcome and safe.
-
-Reports should be directed to *cel-conduct@google.com*, the Project Steward(s)
-for *Common Expression Language (CEL)*. It is the Project Steward’s duty to
-receive and address reported violations of the code of conduct. They will then
-work with a committee consisting of representatives from the Open Source
-Programs Office and the Google Open Source Strategy team. If for any reason you
-are uncomfortable reaching out to the Project Steward, please email
-opensource@google.com.
-
-We will investigate every complaint, but you may not receive a direct response.
-We will use our discretion in determining when and how to follow up on reported
-incidents, which may range from not taking action to permanent expulsion from
-the project and project-sponsored spaces. We will notify the accused of the
-report and provide them an opportunity to discuss it before any action is taken.
-The identity of the reporter will be omitted from the details of the report
-supplied to the accused. In potentially harmful situations, such as ongoing
-harassment or threats to anyone's safety, we may take action without notice.
-
-## Attribution
-
-This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
-available at
-https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 47634bc4c..7a5964ff1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,33 +1,3 @@
# How to Contribute
-We'd love to accept your patches and contributions to this project.
-
-## Before you begin
-
-### Sign our Contributor License Agreement
-
-Contributions to this project must be accompanied by a
-[Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
-You (or your employer) retain the copyright to your contribution; this simply
-gives us permission to use and redistribute your contributions as part of the
-project.
-
-If you or your current employer have already signed the Google CLA (even if it
-was for a different project), you probably don't need to do it again.
-
-Visit to see your current agreements or to
-sign a new one.
-
-### Review our Community Guidelines
-
-This project follows [Google's Open Source Community
-Guidelines](https://opensource.google/conduct/).
-
-## Contribution process
-
-### Code Reviews
-
-All submissions, including submissions by project members, require review. We
-use GitHub pull requests for this purpose. Consult
-[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
-information on using pull requests.
\ No newline at end of file
+Contact Evolveum at info@evolveum.com.
diff --git a/MODULE.bazel b/MODULE.bazel
index fcaf041ba..1175c2619 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -46,7 +46,7 @@ TRUTH_VERSION = "1.4.4"
PROTOBUF_JAVA_VERSION = "4.33.5"
-CEL_VERSION = "0.13.1"
+CEL_VERSION = "0.13.1e-SNAPSHOT"
# Compile only artifacts
[
@@ -119,20 +119,21 @@ maven.install(
# Conformance test only
-maven.install(
- name = "maven_conformance",
- artifacts = [
- "dev.cel:cel:" + CEL_VERSION,
- "dev.cel:compiler:" + CEL_VERSION,
- "dev.cel:runtime:" + CEL_VERSION,
- ],
- repositories = [
- "https://maven.google.com",
- "https://repo1.maven.org/maven2",
- "https://central.sonatype.com/repository/maven-snapshots/",
- ],
-)
-use_repo(maven, "maven", "maven_android", "maven_conformance")
+#maven.install(
+# name = "maven_conformance",
+# artifacts = [
+# "dev.cel:cel:" + CEL_VERSION,
+# "dev.cel:compiler:" + CEL_VERSION,
+# "dev.cel:runtime:" + CEL_VERSION,
+# ],
+# repositories = [
+# "https://maven.google.com",
+# "https://repo1.maven.org/maven2",
+# "https://central.sonatype.com/repository/maven-snapshots/",
+# ],
+#)
+#use_repo(maven, "maven", "maven_android", "maven_conformance")
+use_repo(maven, "maven", "maven_android")
non_module_dependencies = use_extension("//:repositories.bzl", "non_module_dependencies")
use_repo(non_module_dependencies, "antlr4_jar")
diff --git a/README.md b/README.md
index e2424a85c..0c8154c41 100644
--- a/README.md
+++ b/README.md
@@ -1,389 +1,54 @@
-# Common Expression Language for Java
+# Evolveum Fork of cel-java
-The Common Expression Language (CEL) is a non-Turing complete language designed
-for simplicity, speed, safety, and portability. CEL's C-like [syntax][1] looks
-nearly identical to equivalent expressions in C++, Go, Java, and TypeScript.
+This project contains fork of [cel-java project](https://github.com/cel-expr/cel-java/), a Java implementation of Common Expression Language.
-```java
-// Check whether a resource name starts with a group name.
-resource.name.startsWith("/groups/"+auth.claims.group)
-```
-
-```go
-// Determine whether the request is in the permitted time window.
-request.time - resource.age < duration("24h")
-```
-
-```typescript
-// Check whether all resource names in a list match a given filter.
-auth.claims.email_verified && resources.all(r, r.startsWith(auth.claims.email))
-```
-
-A CEL "program" is a single expression. The examples have been tagged as
-`java`, `go`, and `typescript` within the markdown to showcase the commonality
-of the syntax.
-
-CEL is ideal for lightweight expression evaluation when a fully sandboxed
-scripting language is too resource intensive.
-
----
-
-* [Quick Start](#quick-start)
-* [Overview](#overview)
- * [Environment Setup](#environment-setup)
- * [Parsing](#parsing)
- * [Checking](#checking)
- * [Macros](#macros)
- * [Evaluation](#evaluation)
- * [Errors](#errors)
- * [Extensions](#extensions)
-* [Install](#install)
-* [Common Questions](#common-questions)
-* [License](#license)
-
----
-
-## Quick Start
-
-### Install
-
-CEL-Java is available in Maven Central Repository. [Download the JARs here][8] or add the following to your build dependencies:
-
-**Maven (pom.xml)**:
-
-```xml
-
- dev.cel
- cel
- 0.13.1
-
-```
-
-**Gradle**
-
-```gradle
-implementation 'dev.cel:cel:0.13.1'
-```
-
-Then run this example:
-
-```java
-import dev.cel.common.CelAbstractSyntaxTree;
-import dev.cel.common.CelValidationException;
-import dev.cel.common.types.SimpleType;
-import dev.cel.compiler.CelCompiler;
-import dev.cel.compiler.CelCompilerFactory;
-import dev.cel.runtime.CelEvaluationException;
-import dev.cel.runtime.CelRuntime;
-import dev.cel.runtime.CelRuntimeFactory;
-import java.util.Map;
+## How Is This Fork Different?
-public class HelloWorld {
- // Construct the compilation and runtime environments.
- // These instances are immutable and thus trivially thread-safe and amenable to caching.
- private static final CelCompiler CEL_COMPILER =
- CelCompilerFactory.standardCelCompilerBuilder().addVar("my_var", SimpleType.STRING).build();
- private static final CelRuntime CEL_RUNTIME =
- CelRuntimeFactory.standardCelRuntimeBuilder().build();
+This fork brings **nullability** to the CEL world.
+The code of the CEL engine is modified to work seamlessly with null values in CEL expressions.
+E.g., following CEL expression fails miserably in original CEL implementation, in case there are null values in the "focus" data structure.
- public void run() throws CelValidationException, CelEvaluationException {
- // Compile the expression into an Abstract Syntax Tree.
- CelAbstractSyntaxTree ast = CEL_COMPILER.compile("my_var + '!'").getAst();
-
- // Plan an executable program instance.
- CelRuntime.Program program = CEL_RUNTIME.createProgram(ast);
-
- // Evaluate the program with an input variable.
- String result = (String) program.eval(Map.of("my_var", "Hello World"));
- System.out.println(result); // 'Hello World!'
- }
-}
+```cel
+focus.activation.administrativeStatus == 'enabled'
```
-## Overview
-
-Determine the variables and functions you want to provide to CEL. Parse and
-check an expression to make sure it's valid. Then evaluate the output AST
-against some input. Checking is optional, but strongly encouraged.
-
-### Environment Setup
-
-Configuration for the entire CEL stack can be done all at once via the
-`CelFactory.standardCelBuilder()`, or can be composed into compilation and
-evaluation via the `CelCompilerFactory` and `CelRuntimeFactory`.
-
-The simplest form of CEL usage is as follows:
-
-```java
-Cel cel = CelFactory.standardCelBuilder().build();
-```
-
-More commonly, your application will want to configure type-checking separately
-from the runtime. Use `CelCompilerFactory` to construct a compilation
-environment and declare the types, macros, variables, and functions to use with
-your CEL application:
-
-```java
-// Example environment for the following expression:
-// resource.name.startsWith('/groups/' + group)
-CelCompiler cel = CelCompilerFactory.standardCelCompilerBuilder()
- .setStandardMacros(CelStandardMacro.HAS)
- .setContainer("google.rpc.context.AttributeContext")
- .addMessageTypes(AttributeContext.getDescriptor())
- .addVar("resource",
- StructTypeReference.create("google.rpc.context.AttributeContext.Resource"))
- .addVar("group", SimpleType.STRING)
- .build();
-```
-
-More information about the features which are supported on the builder may be
-found in the [`CelCompilerBuilder`][9].
-
-### Parsing
-
-Some CEL use cases only require parsing of an expression in order to be useful.
-For example, one example might want to check whether the expression contains any
-nested comprehensions, or possibly to pass the parsed expression to a C++ or Go
-binary for evaluation. Presently, Java does not support parse-only evaluation.
-
-```java
-CelValidationResult parseResult =
- cel.parse("resource.name.startsWith('/groups/' + group)");
-try {
- return parseResult.getAst();
-} catch (CelValidationException e) {
- // Handle exception...
-}
-```
-
-### Checking
-
-Type-checking is performed on `CelAbstractSyntaxTree` values to ensure that the
-expression is well formed and all variable and function references are defined.
-
-Type-checking can be performed immediately after parsing an expression:
-
-```java
-try {
- CelValidationResult parseResult =
- cel.parse("resource.name.startsWith('/groups/' + group)");
- CelValidationResult checkResult = cel.check(parseResult.getAst());
- return checkResult.getAst();
-} catch (CelValidationException e) {
- // Handle exception...
-}
-```
-
-Or, the parse and type-check can be combined into the `compile` call. This is
-likely the more common need.
-
-```java
-CelValidationResult compileResult =
- cel.compile("resource.name.startsWith('/groups/' + group)");
-try {
- return compileResult.getAst();
-} catch (CelValidationException e) {
- // Handle exception...
-}
-```
-
-#### Macros
-
-Macros were introduced to support optional CEL features that might not be
-desired in all use cases without the syntactic burden and complexity such
-features might desire if they were part of the core CEL syntax. Macros are
-expanded at parse time and their expansions are type-checked at check time.
-
-For example, when macros are enabled it is possible to support bounded iteration
-/ fold operators. The macros `all`, `exists`, `exists_one`, `filter`, and `map`
-are particularly useful for evaluating a single predicate against list and map
-values.
-
-```javascript
-// Ensure all tweets are less than 140 chars
-tweets.all(t, t.size() <= 140)
-```
-
-The `has` macro is useful for unifying field presence testing logic across
-protobuf types and dynamic (JSON-like) types.
-
-```javascript
-// Test whether the field is a non-default value if proto-based, or defined
-// in the JSON case.
-has(message.field)
-```
-
-Both cases traditionally require special syntax at the language level, but these
-features are exposed via macros in CEL.
-
-Refer to the [CEL Specification][10] for full listings of available macros. To
-leverage them, simply set the desired macros via `setStandardMacros` on the
-builder:
-
-```java
-CelCompiler.standardCelBuilder()
- .setStandardMacros(CelStandardMacro.STANDARD_MACROS)
-```
-
-### Evaluation
-
-Expressions can be evaluated using once they are type-checked/compiled by
-creating a `CelRuntime.Program` from a `CelAbstractSyntaxTree`:
-
-```java
-CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder().build();
-try {
- CelRuntime.Program program = celRuntime.createProgram(compileResult.getAst());
- return program.eval(
- ImmutableMap.of(
- "resource", Resource.newBuilder().setName("/groups/").build(),
- "group", "admin"
- ));
-} catch (CelEvaluationException e) {
- // Handle evaluation exceptions ...
-}
-```
-
-The evaluation is thread-safe and side effect free thus many different inputs can
-be sent to the same `cel.Program`.
-
-#### Partial State
-
-In distributed apps it is not uncommon to have edge caches and central services.
-If possible, evaluation should happen at the edge, but it isn't always possible
-to know the full state required for all values and functions present in the CEL
-expression.
-
-To improve the odds of successful evaluation with partial state, CEL uses
-commutative logical operators `&&`, `||`. If an error or unknown value (not the
-same thing) is encountered on the left-hand side, the right-hand side is
-evaluated also to determine the outcome. While it is possible to implement
-evaluation with partial state without this feature, this method was chosen
-because it aligns with the semantics of SQL evaluation and because it's more
-robust to evaluation against dynamic data types such as JSON inputs.
-
-In the following truth-table, the symbols `` and `` represent error or
-unknown values, with the `?` indicating that the branch is not taken due to
-short-circuiting. When the result is `` this means that both the args are
-possibly relevant to the result.
-
-| Expression | Result |
-|---------------------|----------|
-| `false && ?` | `false` |
-| `true && false` | `false` |
-| ` && false` | `false` |
-| `true && true` | `true` |
-| `true && ` | `` |
-| ` && true` | `` |
-| ` && ` | `` |
-| `true \|\| ?` | `true` |
-| `false \|\| true` | `true` |
-| ` \|\| true` | `true` |
-| `false \|\| false` | `false` |
-| `false \|\| ` | `` |
-| ` \|\| false` | `` |
-| ` \|\| ` | `` |
-
-### Errors
-
-Parse and check errors have friendly error messages with pointers to where the
-issues occur in source:
-
-```sh
-ERROR: :1:40: undefined field 'undefined'
- | TestAllTypes{single_int32: 1, undefined: 2}
- | .......................................^`,
-```
-
-Both the parsed and checked expressions contain source position information
-about each node that appears in the output AST. This information can be used
-to determine error locations at evaluation time as well.
-
-### Extensions
-
-CEL-Java offers a suite of [canonical extensions][11] to support commonly
-needed features that falls outside the CEL specification.
-
-Examples:
-
-```java
-// String manipulation
-'hello hello'.replace('he', 'we') // returns 'wello wello'
-'hello hello hello'.split(' ') // returns ['hello', 'hello', 'hello']
-
-// Math extensions
-math.greatest(-42.0, -21.5, -100.0) // -21.5
-math.least(-42.0, -21.5, -100.0) // -100.0
-
-// Proto extensions
-proto.getExt(msg, google.expr.proto2.test.int32_ext) // returns int value
-
-// Local bindings
-cel.bind(a, 'hello',
- cel.bind(b, 'world', a + b + b + a)) // "helloworldworldhello"
-```
-
-## Common Questions
-
-### Why not JavaScript, Lua, or WASM?
-
-JavaScript and Lua are rich languages that require sandboxing to execute safely.
-Sandboxing is costly and factors into the "what will I let users evaluate?"
-question heavily when the answer is anything more than O(n)
-complexity.
+This fork brings null-tolerant implementation of CEL.
+The expression above works well when interpreted by the code in this fork, with any combination of null values.
+Expression authors do not need to care about null values most of the time, and they can focus on the business aspects of the expression, as they are supposed to.
-CEL evaluates linearly with respect to the size of the expression and the input
-being evaluated when macros are disabled. The only functions beyond the
-built-ins that may be invoked are provided by the host environment. While
-extension functions may be more complex, this is a choice by the application
-embedding CEL.
+## Purpose, Security and Quality
-But, why not WASM? WASM is an excellent choice for certain applications and is
-far superior to embedded JavaScript and Lua, but it does not have support for
-garbage collection and non-primitive object types require semi-expensive calls
-across modules. In most cases CEL will be faster and just as portable for its
-intended use case, though for node.js and web-based execution CEL too may offer
-a WASM evaluator with direct to WASM compilation.
+This code is maintained solely for the use in [midPoint](https://github.com/Evolveum/midpoint).
+Tests in this fork are not maintained, as we were not able to make the test work.
+Honestly, we do not think it is work the effort to fight a serious battle with Bazel just to be able to run the tests.
+The code in this fork in tested indirectly, using tests in midPoint.
+However, these tests are testing only the scenarios that midPoint is using.
-### Do I need to Parse _and_ Check?
+Therefore, there are **no guarantees** that this code will work without midPoint, and absolutely no guarantees regarding security of the code when used outside midPoint.
-Checking is an optional, but strongly suggested, step in CEL expression
-validation. It is sufficient in some cases to simply Parse and rely on the
-runtime bindings and error handling to do the right thing.
+Use at your own risk.
-### Where can I learn more about the language?
+## Why Fork?
-* See the [CEL Spec][1] for the specification and conformance test suite.
-* Ask for support on the [CEL Java Discuss][2] Google group.
+Why are we forking and not contributing back to the original project instead?
-### How can I contribute?
+Simply speaking, mostly due to the Google CLA.
+We are not willing to sign it, which is showstopper for any contribution.
-* See [CONTRIBUTING.md](./CONTRIBUTING.md) to get started.
-* Use [GitHub Issues][7] to request features or report bugs.
+However, there seems to be also deeper incompatibilities of this fork with the approach of original team, which makes the contribution unlikely to be accepted even if we did sign the CLA.
-### Dependencies
+Overall, CEL seems to suffer from serious nullophobia (irrational fear of null values).
+This fork is fixing that.
-Java 8 or newer is required.
+To be fair, CEL introduced "optionals" to make working with null values possible.
+However, using optionals is not easy, especially for people who are not expert programmers.
+This would not work for us.
+Null-tolerant language is much better way that optionals for our use cases.
-| Library | Details |
-|-----------------------|----------------------|
-| [Guava][3] | N/A |
-| [RE2/J][4] | N/A |
-| [Protocol Buffers][5] | Full or lite runtime |
-| [ANTLR4][6] | Java runtime |
+Also, we have to make a serious butchery of the basel-based build configuration, to be able to build the project at all, without dependency on Google infrastructure.
-## License
+## Build
-Released under the [Apache License](LICENSE).
+Use `build.sh` to build the project (Bazel is required).
-[1]: https://github.com/cel-expr/cel-spec
-[2]: https://groups.google.com/forum/#!forum/cel-java-discuss
-[3]: https://github.com/google/guava
-[4]: https://github.com/google/re2j
-[5]: https://github.com/protocolbuffers/protobuf/tree/master/java
-[6]: https://github.com/antlr/antlr4/tree/master/runtime/Java
-[7]: https://github.com/cel-expr/cel-java/issues
-[8]: https://search.maven.org/search?q=g:dev.cel
-[9]: https://github.com/cel-expr/cel-java/blob/main/compiler/src/main/java/dev/cel/compiler/CelCompilerBuilder.java
-[10]: https://github.com/cel-expr/cel-spec/blob/master/doc/langdef.md#macros
-[11]: https://github.com/cel-expr/cel-java/blob/main/extensions/src/main/java/dev/cel/extensions/README.md
+Use `publish/publish.sh` to publish Maven artifacts.
diff --git a/build.sh b/build.sh
new file mode 100755
index 000000000..6d471e54e
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+bazel build ... --java_language_version=8 --java_runtime_version=8 --build_tag_filters=-conformance_maven
diff --git a/checker/src/main/java/dev/cel/checker/BUILD.bazel b/checker/src/main/java/dev/cel/checker/BUILD.bazel
index 99d4da586..6afe2a4e6 100644
--- a/checker/src/main/java/dev/cel/checker/BUILD.bazel
+++ b/checker/src/main/java/dev/cel/checker/BUILD.bazel
@@ -98,6 +98,7 @@ java_library(
java_library(
name = "checker_builder",
srcs = CHECKER_BUILDER_SOURCES,
+ visibility = ["//visibility:public"],
tags = [
],
deps = [
diff --git a/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java
index 53615604f..15f394771 100644
--- a/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java
+++ b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java
@@ -381,10 +381,10 @@ public CelOverloadDecl celOverloadDecl() {
public enum Relation implements StandardOverload {
EQUALS(
CelOverloadDecl.newGlobalOverload(
- "equals", "equality", SimpleType.BOOL, TYPE_PARAM_A, TYPE_PARAM_A)),
+ "equals", "equality", SimpleType.BOOL, SimpleType.ANY, SimpleType.ANY)),
NOT_EQUALS(
CelOverloadDecl.newGlobalOverload(
- "not_equals", "inequality", SimpleType.BOOL, TYPE_PARAM_A, TYPE_PARAM_A)),
+ "not_equals", "inequality", SimpleType.BOOL, SimpleType.ANY, SimpleType.ANY)),
;
private final CelOverloadDecl celOverloadDecl;
diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel
index 38548744c..7a6e96c6e 100644
--- a/common/src/main/java/dev/cel/common/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/BUILD.bazel
@@ -106,7 +106,9 @@ java_library(
# used_by_android
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
+# "@maven_conformance//:dev_cel_common",
"//:auto_value",
"//common/annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
@@ -118,6 +120,7 @@ java_library(
srcs = PROTO_AST_SOURCE,
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
":cel_ast",
":cel_source",
@@ -358,6 +361,7 @@ java_library(
srcs = ["CelContainer.java"],
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
"//:auto_value",
"@maven//:com_google_errorprone_error_prone_annotations",
diff --git a/common/src/main/java/dev/cel/common/CelOptions.java b/common/src/main/java/dev/cel/common/CelOptions.java
index d9c2dd818..eeac117de 100644
--- a/common/src/main/java/dev/cel/common/CelOptions.java
+++ b/common/src/main/java/dev/cel/common/CelOptions.java
@@ -1,4 +1,5 @@
// Copyright 2022 Google LLC
+// Portions Copyright 2026 Evolveum
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -120,6 +121,8 @@ public enum ProtoUnsetFieldOptions {
public abstract int maxRegexProgramSize();
+ public abstract boolean enableEasyNull();
+
public abstract Builder toBuilder();
/**
@@ -163,7 +166,8 @@ public static Builder newBuilder() {
.unwrapWellKnownTypesOnFunctionDispatch(true)
.fromProtoUnsetFieldOption(ProtoUnsetFieldOptions.BIND_DEFAULT)
.enableComprehension(true)
- .maxRegexProgramSize(-1);
+ .maxRegexProgramSize(-1)
+ .enableEasyNull(false);
}
/**
@@ -508,6 +512,11 @@ public abstract static class Builder {
*/
public abstract Builder maxRegexProgramSize(int value);
+ /**
+ * TODO
+ */
+ public abstract Builder enableEasyNull(boolean value);
+
/**
* Use the `json_name` field option on a protobuf message as the name of the field.
*
diff --git a/common/src/main/java/dev/cel/common/ast/BUILD.bazel b/common/src/main/java/dev/cel/common/ast/BUILD.bazel
index 3fc709a07..0e4d927b4 100644
--- a/common/src/main/java/dev/cel/common/ast/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/ast/BUILD.bazel
@@ -44,6 +44,7 @@ MUTABLE_EXPR_SOURCES = [
java_library(
name = "ast",
srcs = AST_SOURCES,
+ visibility = ["//visibility:public"],
tags = [
],
deps = [
diff --git a/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java
index d805c9cf4..eee283251 100644
--- a/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java
+++ b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java
@@ -36,7 +36,17 @@ public static CelAttributeNotFoundException forFieldResolution(String... fields)
}
public static CelAttributeNotFoundException forFieldResolution(Collection fields) {
- return new CelAttributeNotFoundException(formatErrorMessage(fields));
+ return new CelAttributeNotFoundException(formatErrorMessage(fields,
+ "Error resolving field%s '%s'. Field selections must be performed on messages or maps."));
+ }
+
+ public static CelAttributeNotFoundException forFieldResolutionList(String... fields) {
+ return forFieldResolutionList(Arrays.asList(fields));
+ }
+
+ public static CelAttributeNotFoundException forFieldResolutionList(Collection fields) {
+ return new CelAttributeNotFoundException(formatErrorMessage(fields,
+ "Error resolving field%s '%s'. Attempted field selections on multi-valued item."));
}
public static CelAttributeNotFoundException forMissingAttributes(Collection attributes) {
@@ -44,15 +54,14 @@ public static CelAttributeNotFoundException forMissingAttributes(Collection fields) {
+ private static String formatErrorMessage(Collection fields, String format) {
String maybePlural = "";
if (fields.size() > 1) {
maybePlural = "s";
}
return String.format(
- "Error resolving field%s '%s'. Field selections must be performed on messages or maps.",
- maybePlural, String.join(", ", fields));
+ format, maybePlural, String.join(", ", fields));
}
private CelAttributeNotFoundException(String message) {
diff --git a/common/src/main/java/dev/cel/common/types/BUILD.bazel b/common/src/main/java/dev/cel/common/types/BUILD.bazel
index de65d0b1f..fb5b7e2d4 100644
--- a/common/src/main/java/dev/cel/common/types/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/types/BUILD.bazel
@@ -46,6 +46,7 @@ java_library(
srcs = CEL_TYPE_SOURCES,
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
":type_providers",
"//:auto_value",
@@ -168,6 +169,7 @@ java_library(
srcs = [
"ProtoMessageTypeProvider.java",
],
+ visibility = ["//visibility:public"],
tags = [
],
deps = [
diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel
index 5ccc498fd..bedcfc80b 100644
--- a/common/src/main/java/dev/cel/common/values/BUILD.bazel
+++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel
@@ -161,6 +161,7 @@ java_library(
srcs = CEL_VALUES_SOURCES,
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
":cel_byte_string",
":cel_value",
diff --git a/compiler/src/main/java/dev/cel/compiler/BUILD.bazel b/compiler/src/main/java/dev/cel/compiler/BUILD.bazel
index 6a9a80709..9843896e0 100644
--- a/compiler/src/main/java/dev/cel/compiler/BUILD.bazel
+++ b/compiler/src/main/java/dev/cel/compiler/BUILD.bazel
@@ -59,6 +59,7 @@ java_library(
srcs = COMPILER_BUILDER_SOURCES,
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
"//:auto_value",
"//checker:checker_builder",
diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel
index e9ed58642..147fc031f 100644
--- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel
+++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel
@@ -43,11 +43,11 @@ java_library(
)
MAVEN_JAR_DEPS = [
- "@maven_conformance//:dev_cel_compiler",
- "@maven_conformance//:dev_cel_common",
- "@maven_conformance//:dev_cel_runtime",
- "@maven_conformance//:dev_cel_protobuf",
- "@maven_conformance//:dev_cel_cel",
+# "@maven_conformance//:dev_cel_compiler",
+# "@maven_conformance//:dev_cel_common",
+# "@maven_conformance//:dev_cel_runtime",
+# "@maven_conformance//:dev_cel_protobuf",
+# "@maven_conformance//:dev_cel_cel",
]
java_library(
@@ -58,6 +58,7 @@ java_library(
deps = MAVEN_JAR_DEPS + [
"//:java_truth",
"//compiler:compiler_builder",
+ "//extensions:extension_library",
"//parser:parser_factory",
"//runtime:runtime_planner_impl",
"//testing:expr_value_utils",
diff --git a/conformance/src/test/java/dev/cel/maven/BUILD.bazel b/conformance/src/test/java/dev/cel/maven/BUILD.bazel
index 895339521..50cc55127 100644
--- a/conformance/src/test/java/dev/cel/maven/BUILD.bazel
+++ b/conformance/src/test/java/dev/cel/maven/BUILD.bazel
@@ -6,15 +6,15 @@ package(default_applicable_licenses = [
# keep sorted
MAVEN_COMPILER_JAR_DEPS = [
- "@maven_conformance//:dev_cel_common",
- "@maven_conformance//:dev_cel_compiler",
+# "@maven_conformance//:dev_cel_common",
+# "@maven_conformance//:dev_cel_compiler",
]
# keep sorted
MAVEN_RUNTIME_JAR_DEPS = [
- "@maven_conformance//:dev_cel_common",
- "@maven_conformance//:dev_cel_protobuf",
- "@maven_conformance//:dev_cel_runtime",
+# "@maven_conformance//:dev_cel_common",
+# "@maven_conformance//:dev_cel_protobuf",
+# "@maven_conformance//:dev_cel_runtime",
]
java_test(
@@ -24,6 +24,22 @@ java_test(
deps =
MAVEN_COMPILER_JAR_DEPS + [
"//:java_truth",
+ "//common:cel_ast",
+ "//common:compiler_common",
+ "//compiler",
+ "//checker",
+ "//parser/src/main/java/dev/cel/parser:parser_factory",
+ "//parser/src/main/java/dev/cel/parser:unparser",
+ "//runtime:evaluation_exception",
+ "//checker/src/main/java/dev/cel/checker:checker_builder",
+ "//common/src/main/java/dev/cel/common/ast",
+ "//common/src/main/java/dev/cel/common/types:message_type_provider",
+ "//common/src/main/java/dev/cel/common/types",
+ "//common/src/main/java/dev/cel/common:container",
+ "//common/src/main/java/dev/cel/common:options",
+ "//compiler/src/main/java/dev/cel/compiler:compiler_builder",
+ "//parser/src/main/java/dev/cel/parser:macro",
+ "//parser/src/main/java/dev/cel/parser:parser_builder",
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
"@maven//:com_google_testparameterinjector_test_parameter_injector",
"@maven//:junit_junit",
@@ -37,6 +53,24 @@ java_test(
deps =
MAVEN_RUNTIME_JAR_DEPS + [
"//:java_truth",
+ "//common:cel_ast",
+ "//common:compiler_common",
+ "//common:options",
+ "//runtime:evaluation_exception",
+ "//runtime:function_binding",
+ "//runtime:runtime_factory",
+ "//common/src/main/java/dev/cel/common:proto_ast",
+ "//runtime/src/main/java/dev/cel/runtime",
+ "//runtime/src/main/java/dev/cel/runtime:standard_functions",
+ "//checker/src/main/java/dev/cel/checker:checker_builder",
+ "//common/src/main/java/dev/cel/common/ast",
+ "//common/src/main/java/dev/cel/common/types:message_type_provider",
+ "//common/src/main/java/dev/cel/common/types",
+ "//common/src/main/java/dev/cel/common:container",
+ "//common/src/main/java/dev/cel/common:options",
+ "//compiler/src/main/java/dev/cel/compiler:compiler_builder",
+ "//parser/src/main/java/dev/cel/parser:macro",
+ "//parser/src/main/java/dev/cel/parser:parser_builder",
"@cel_spec//proto/cel/expr:checked_java_proto",
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
"@maven//:com_google_guava_guava",
diff --git a/parser/src/main/java/dev/cel/parser/BUILD.bazel b/parser/src/main/java/dev/cel/parser/BUILD.bazel
index e32c50ee8..96e4af5f7 100644
--- a/parser/src/main/java/dev/cel/parser/BUILD.bazel
+++ b/parser/src/main/java/dev/cel/parser/BUILD.bazel
@@ -42,6 +42,7 @@ java_library(
srcs = ["CelParserFactory.java"],
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
":parser",
"//common:options",
@@ -80,6 +81,7 @@ java_library(
srcs = PARSER_BUILDER_SOURCES,
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
":macro",
"//common:cel_source",
@@ -94,6 +96,7 @@ java_library(
srcs = MACRO_SOURCES,
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
"//:auto_value",
"//common:compiler_common",
@@ -111,6 +114,7 @@ java_library(
srcs = UNPARSER_SOURCES,
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
":unparser_visitor",
"//common:cel_ast",
diff --git a/publish/cel_version.bzl b/publish/cel_version.bzl
index 4ceb4bfa4..c1a948f77 100644
--- a/publish/cel_version.bzl
+++ b/publish/cel_version.bzl
@@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Maven artifact version for CEL."""
-CEL_VERSION = "0.13.1"
+CEL_VERSION = "0.13.1e-SNAPSHOT"
diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
index 17160e346..7fe6b8739 100644
--- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
+++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
@@ -108,6 +108,7 @@ java_library(
"//common/internal:proto_message_factory",
"//common/types:cel_types",
"//common/values:cel_byte_string",
+ "//common/src/main/java/dev/cel/common/values",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
@@ -319,6 +320,7 @@ java_library(
"//common/types",
"//common/types:type_providers",
"//common/values:cel_byte_string",
+ "//common/src/main/java/dev/cel/common/values",
"@maven//:com_google_code_findbugs_annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
@@ -357,6 +359,7 @@ cel_android_library(
"//common/types:type_providers_android",
"//common/types:types_android",
"//common/values:cel_byte_string",
+ "//common/src/main/java/dev/cel/common/values",
"@maven//:com_google_code_findbugs_annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
@@ -623,6 +626,7 @@ java_library(
srcs = ["CelStandardFunctions.java"],
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
":function_binding",
":runtime_equality",
@@ -795,10 +799,14 @@ java_library(
srcs = [
"CelFunctionOverload.java",
"OptimizedFunctionOverload.java",
+ "NullabilityProperties.java"
],
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
+# "@maven_conformance//:dev_cel_common",
+ "//common/values",
":evaluation_exception",
":unknown_attributes",
"@maven//:com_google_errorprone_error_prone_annotations",
@@ -811,10 +819,13 @@ cel_android_library(
srcs = [
"CelFunctionOverload.java",
"OptimizedFunctionOverload.java",
+ "NullabilityProperties.java"
],
+ visibility = ["//visibility:public"],
deps = [
":evaluation_exception",
":unknown_attributes_android",
+ "//common/values",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven_android//:com_google_guava_guava",
],
@@ -930,6 +941,7 @@ java_library(
srcs = RUNTIME_SOURCES,
tags = [
],
+ visibility = ["//visibility:public"],
deps = [
":activation",
":evaluation_exception",
@@ -1301,10 +1313,11 @@ cel_android_library(
java_library(
name = "resolved_overload",
- srcs = ["CelResolvedOverload.java"],
+ srcs = ["CelResolvedOverload.java", "CelResolvedNullOverload.java"],
tags = [
],
deps = [
+# "@maven_conformance//:dev_cel_runtime",
":evaluation_exception",
":function_binding",
":function_overload",
@@ -1318,7 +1331,7 @@ java_library(
cel_android_library(
name = "resolved_overload_android",
- srcs = ["CelResolvedOverload.java"],
+ srcs = ["CelResolvedOverload.java", "CelResolvedNullOverload.java"],
tags = [
],
deps = [
diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java
index 98991d383..a7bd4cb1f 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java
@@ -1,4 +1,5 @@
// Copyright 2025 Google LLC
+// Portions Copyright 2026 Evolveum
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -50,10 +51,19 @@ public interface CelFunctionBinding {
boolean isStrict();
+ NullabilityProperties getNullabilityProperties();
+
/** Create a unary function binding from the {@code overloadId}, {@code arg}, and {@code impl}. */
@SuppressWarnings("unchecked") // Safe from CelFunctionOverload.canHandle check before invocation
static CelFunctionBinding from(
- String overloadId, Class arg, CelFunctionOverload.Unary impl) {
+ String overloadId, Class arg, CelFunctionOverload.Unary impl) {
+ return from(overloadId, arg, impl, determineNullability(ImmutableList.of(arg)));
+ }
+
+ /** Create a unary function binding from the {@code overloadId}, {@code arg}, and {@code impl}. */
+ @SuppressWarnings("unchecked") // Safe from CelFunctionOverload.canHandle check before invocation
+ static CelFunctionBinding from(
+ String overloadId, Class arg, CelFunctionOverload.Unary impl, NullabilityProperties nullabilityProperties) {
return from(
overloadId,
ImmutableList.of(arg),
@@ -67,16 +77,26 @@ public Object apply(Object[] args) throws CelEvaluationException {
public Object apply(Object arg1) throws CelEvaluationException {
return impl.apply((T) arg1);
}
- });
+ },
+ nullabilityProperties);
}
- /**
- * Create a binary function binding from the {@code overloadId}, {@code arg1}, {@code arg2}, and
- * {@code impl}.
- */
+ /**
+ * Create a binary function binding from the {@code overloadId}, {@code arg1}, {@code arg2}, and
+ * {@code impl}.
+ */
+ @SuppressWarnings("unchecked") // Safe from CelFunctionOverload.canHandle check before invocation
+ static CelFunctionBinding from(
+ String overloadId, Class arg1, Class arg2, CelFunctionOverload.Binary impl) {
+ return from(overloadId, arg1, arg2, impl, determineNullability(ImmutableList.of(arg1, arg2)));
+ }
+
@SuppressWarnings("unchecked") // Safe from CelFunctionOverload.canHandle check before invocation
static CelFunctionBinding from(
- String overloadId, Class arg1, Class arg2, CelFunctionOverload.Binary impl) {
+ String overloadId,
+ Class arg1, Class arg2,
+ CelFunctionOverload.Binary impl,
+ NullabilityProperties nullabilityProperties) {
return from(
overloadId,
ImmutableList.of(arg1, arg2),
@@ -90,18 +110,44 @@ public Object apply(Object[] args) throws CelEvaluationException {
public Object apply(Object arg1, Object arg2) throws CelEvaluationException {
return impl.apply((T1) arg1, (T2) arg2);
}
- });
+ },
+ nullabilityProperties);
}
/** Create a function binding from the {@code overloadId}, {@code argTypes}, and {@code impl}. */
static CelFunctionBinding from(
String overloadId, Iterable> argTypes, CelFunctionOverload impl) {
+ return from(overloadId, argTypes, impl, determineNullability(argTypes));
+ }
+
+ static CelFunctionBinding from(
+ String overloadId, Iterable> argTypes,
+ CelFunctionOverload impl,
+ NullabilityProperties nullabilityProperties) {
return new FunctionBindingImpl(
- overloadId, ImmutableList.copyOf(argTypes), impl, /* isStrict= */ true);
+ overloadId, ImmutableList.copyOf(argTypes), impl, /* isStrict= */ true, nullabilityProperties);
+ }
+
+ // TODO: find better place? + consider CelOptions
+ static NullabilityProperties determineNullability(Iterable> argTypes) {
+ for( Class> argType : argTypes) {
+ // If overload declaration is using primitive type then it cannot accept null
+ if (argType.isPrimitive()) {
+ return NullabilityProperties.NOT_NULLABLE;
+ }
+ }
+ return NullabilityProperties.NULLABLE;
+ }
+
+ static NullabilityProperties mergeNullabilityProperties(Collection nullabilityProperties) {
+ if (nullabilityProperties.stream().distinct().count() > 1) {
+ throw new IllegalArgumentException("Cannot merge nullability properties");
+ }
+ return nullabilityProperties.iterator().next();
}
- /** See {@link #fromOverloads(String, Collection)}. */
+ /** See {@link #fromOverloads(String, Collection)}. */
static ImmutableSet fromOverloads(
String functionName, CelFunctionBinding... overloadBindings) {
return fromOverloads(functionName, ImmutableList.copyOf(overloadBindings));
diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java
index c5f75096d..8fa93188e 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java
@@ -1,4 +1,5 @@
// Copyright 2024 Google LLC
+// Portions Copyright 2026 Evolveum
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,6 +18,8 @@
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.Immutable;
import java.util.Map;
+import java.util.Optional;
+import dev.cel.common.values.NullValue;
/** Interface describing the general signature of all CEL custom function implementations. */
@Immutable
@@ -51,14 +54,14 @@ interface Binary {
* Returns true if the overload's expected argument types match the types of the given arguments.
*/
static boolean canHandle(
- Object[] arguments, ImmutableList> parameterTypes, boolean isStrict) {
+ Object[] arguments, ImmutableList> parameterTypes, boolean isStrict, NullabilityProperties nullabilityProperties) {
if (parameterTypes.size() != arguments.length) {
return false;
}
for (int i = 0; i < parameterTypes.size(); i++) {
Class> paramType = parameterTypes.get(i);
Object arg = arguments[i];
- boolean result = canHandleArg(arg, paramType, isStrict);
+ boolean result = canHandleArg(arg, paramType, isStrict, nullabilityProperties);
if (!result) {
return false;
}
@@ -66,23 +69,27 @@ static boolean canHandle(
return true;
}
- static boolean canHandle(Object arg, ImmutableList> parameterTypes, boolean isStrict) {
+ static boolean canHandle(Object arg, ImmutableList> parameterTypes, boolean isStrict, NullabilityProperties nullabilityProperties) {
if (parameterTypes.size() != 1) {
return false;
}
- return canHandleArg(arg, parameterTypes.get(0), isStrict);
+ return canHandleArg(arg, parameterTypes.get(0), isStrict, nullabilityProperties);
}
static boolean canHandle(
- Object arg1, Object arg2, ImmutableList> parameterTypes, boolean isStrict) {
+ Object arg1, Object arg2, ImmutableList> parameterTypes, boolean isStrict, NullabilityProperties nullabilityProperties) {
if (parameterTypes.size() != 2) {
return false;
}
- return canHandleArg(arg1, parameterTypes.get(0), isStrict)
- && canHandleArg(arg2, parameterTypes.get(1), isStrict);
+ return canHandleArg(arg1, parameterTypes.get(0), isStrict, nullabilityProperties)
+ && canHandleArg(arg2, parameterTypes.get(1), isStrict, nullabilityProperties);
}
- static boolean canHandleArg(Object arg, Class> paramType, boolean isStrict) {
+ static boolean canHandleArg(Object arg, Class> paramType, boolean isStrict, NullabilityProperties nullabilityProperties) {
+ if (nullabilityProperties.isNullable() && isNullEquivalent(arg)) {
+ return true;
+ }
+
// null can be assigned to messages, maps, and to objects.
// TODO: Remove null special casing
if (arg == null) {
@@ -100,4 +107,39 @@ static boolean canHandleArg(Object arg, Class> paramType, boolean isStrict) {
return paramType.isAssignableFrom(arg.getClass());
}
+
+ static Object[] reflectNullability(
+ Object[] arguments, NullabilityProperties nullabilityProperties) {
+ if (!nullabilityProperties.isNullable()) {
+ return arguments;
+ }
+ Object[] processedArguments = new Object[arguments.length];
+ for (int i = 0; i < arguments.length; i++) {
+ processedArguments[i] = reflectNullability(arguments[i], nullabilityProperties);
+ }
+ return processedArguments;
+ }
+
+ static Object reflectNullability(
+ Object argument, NullabilityProperties nullabilityProperties) {
+ if (nullabilityProperties.isNullable() && isNullEquivalent(argument)) {
+ return null;
+ }
+ return argument;
+ }
+
+ static boolean isNullEquivalent(Object arg) {
+ if (arg == null) {
+ return true;
+ }
+ if (arg == NullValue.NULL_VALUE) {
+ return true;
+ }
+ if (arg instanceof Optional>) {
+ return !((Optional>)arg).isPresent();
+ }
+ return false;
+ }
+
+ static final Object NULL_VALUE = NullValue.NULL_VALUE;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java
index 2da08120c..d5e3c49c9 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java
@@ -72,6 +72,7 @@ private static CelResolvedOverload createResolvedOverload(CelFunctionBinding bin
binding.getOverloadId(),
binding.getDefinition(),
binding.isStrict(),
+ binding.getNullabilityProperties(),
binding.getArgTypes());
}
}
diff --git a/runtime/src/main/java/dev/cel/runtime/CelResolvedNullOverload.java b/runtime/src/main/java/dev/cel/runtime/CelResolvedNullOverload.java
new file mode 100644
index 000000000..52278f0e4
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/CelResolvedNullOverload.java
@@ -0,0 +1,86 @@
+// Copyright 2026 Evolveum
+//
+// 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.common.collect.ImmutableList;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.common.annotations.Internal;
+import java.util.List;
+
+/**
+ * TODO
+ */
+@Immutable
+@Internal
+public class CelResolvedNullOverload extends CelResolvedOverload {
+
+ private final ImmutableList> parameterTypes;
+ private final NullabilityProperties nullabilityProperties;
+ private final OptimizedFunctionOverload optimizedFunctionOverload;
+
+ CelResolvedNullOverload(ImmutableList> parameterTypes, NullabilityProperties nullabilityProperties) {
+ this.parameterTypes = parameterTypes;
+ this.nullabilityProperties = nullabilityProperties;
+ this.optimizedFunctionOverload = args -> nullabilityProperties.getDefaultFunction().apply(args);
+ }
+ /** The base function name. */
+ public String getFunctionName() {
+ return "nullOverload";
+ }
+
+ /** The overload id of the function. */
+ public String getOverloadId() {
+ return "null-overload";
+ }
+
+ /** The types of the function parameters. */
+ public ImmutableList> getParameterTypes() {
+ return parameterTypes;
+ }
+
+ public boolean isStrict() {
+ return false;
+ }
+
+ public NullabilityProperties getNullabilityProperties() {
+ return nullabilityProperties;
+ }
+
+ /** The function definition. */
+ public CelFunctionOverload getDefinition() {
+ return optimizedFunctionOverload;
+ }
+
+ OptimizedFunctionOverload getOptimizedDefinition() {
+ return optimizedFunctionOverload;
+ }
+
+ public Object invoke(Object[] args) throws CelEvaluationException {
+ return CelFunctionOverload.NULL_VALUE;
+ }
+
+ public Object invoke(Object arg) throws CelEvaluationException {
+ return CelFunctionOverload.NULL_VALUE;
+ }
+
+ public Object invoke(Object arg1, Object arg2) throws CelEvaluationException {
+ return CelFunctionOverload.NULL_VALUE;
+ }
+
+ public static CelResolvedNullOverload of(List> parameterTypes, NullabilityProperties nullabilityProperties) {
+ return new CelResolvedNullOverload(ImmutableList.copyOf(parameterTypes), nullabilityProperties);
+ }
+
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java
index fbe9a3289..04f20eeb2 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java
@@ -1,4 +1,5 @@
// Copyright 2024 Google LLC
+// Portions Copyright 2026 Evolveum
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -53,6 +54,8 @@ public abstract class CelResolvedOverload {
*/
public abstract boolean isStrict();
+ public abstract NullabilityProperties getNullabilityProperties();
+
/** The function definition. */
public abstract CelFunctionOverload getDefinition();
@@ -61,24 +64,26 @@ public abstract class CelResolvedOverload {
public Object invoke(Object[] args) throws CelEvaluationException {
// Note: canHandle check is handled separately in DynamicDispatchOverload
if (isDynamicDispatch()
- || CelFunctionOverload.canHandle(args, getParameterTypes(), isStrict())) {
- return getDefinition().apply(args);
+ || CelFunctionOverload.canHandle(args, getParameterTypes(), isStrict(), getNullabilityProperties())) {
+ return getDefinition().apply(CelFunctionOverload.reflectNullability(args, getNullabilityProperties()));
}
throw new CelOverloadNotFoundException(getFunctionName(), ImmutableList.of(getOverloadId()));
}
public Object invoke(Object arg) throws CelEvaluationException {
if (isDynamicDispatch()
- || CelFunctionOverload.canHandle(arg, getParameterTypes(), isStrict())) {
- return getOptimizedDefinition().apply(arg);
+ || CelFunctionOverload.canHandle(arg, getParameterTypes(), isStrict(), getNullabilityProperties())) {
+ return getOptimizedDefinition().apply(CelFunctionOverload.reflectNullability(arg, getNullabilityProperties()));
}
throw new CelOverloadNotFoundException(getFunctionName(), ImmutableList.of(getOverloadId()));
}
public Object invoke(Object arg1, Object arg2) throws CelEvaluationException {
if (isDynamicDispatch()
- || CelFunctionOverload.canHandle(arg1, arg2, getParameterTypes(), isStrict())) {
- return getOptimizedDefinition().apply(arg1, arg2);
+ || CelFunctionOverload.canHandle(arg1, arg2, getParameterTypes(), isStrict(), getNullabilityProperties())) {
+ return getOptimizedDefinition().apply(
+ CelFunctionOverload.reflectNullability(arg1, getNullabilityProperties()),
+ CelFunctionOverload.reflectNullability(arg2, getNullabilityProperties()));
}
throw new CelOverloadNotFoundException(getFunctionName(), ImmutableList.of(getOverloadId()));
}
@@ -92,8 +97,9 @@ public static CelResolvedOverload of(
String overloadId,
CelFunctionOverload definition,
boolean isStrict,
+ NullabilityProperties nullabilityProperties,
Class>... parameterTypes) {
- return of(functionName, overloadId, definition, isStrict, ImmutableList.copyOf(parameterTypes));
+ return of(functionName, overloadId, definition, isStrict, nullabilityProperties, ImmutableList.copyOf(parameterTypes));
}
/**
@@ -105,6 +111,7 @@ public static CelResolvedOverload of(
String overloadId,
CelFunctionOverload definition,
boolean isStrict,
+ NullabilityProperties nullabilityProperties,
List> parameterTypes) {
OptimizedFunctionOverload optimizedDef =
(definition instanceof OptimizedFunctionOverload)
@@ -115,6 +122,7 @@ public static CelResolvedOverload of(
overloadId,
ImmutableList.copyOf(parameterTypes),
isStrict,
+ nullabilityProperties,
definition,
optimizedDef);
}
@@ -123,7 +131,7 @@ public static CelResolvedOverload of(
* Returns true if the overload's expected argument types match the types of the given arguments.
*/
boolean canHandle(Object[] arguments) {
- return CelFunctionOverload.canHandle(arguments, getParameterTypes(), isStrict());
+ return CelFunctionOverload.canHandle(arguments, getParameterTypes(), isStrict(), getNullabilityProperties());
}
private boolean isDynamicDispatch() {
diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java
index 87f11fde2..25ce604c5 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java
@@ -196,6 +196,8 @@ public interface CelRuntimeBuilder {
@CanIgnoreReturnValue
CelRuntimeBuilder setExtensionRegistry(ExtensionRegistry extensionRegistry);
+ @CanIgnoreReturnValue
+ CelRuntimeBuilder setRuntimeEquality(RuntimeEquality runtimeEquality);
/**
* Set the {@link CelContainer} to use as the namespace for resolving CEL expression variables and
diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java
index b02f64b61..c67eff34c 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java
@@ -95,6 +95,9 @@ public abstract class CelRuntimeImpl implements CelRuntime {
@AutoValue.CopyAnnotations
abstract @Nullable ExtensionRegistry extensionRegistry();
+ @AutoValue.CopyAnnotations
+ abstract @Nullable RuntimeEquality runtimeEquality();
+
@Override
public Program createProgram(CelAbstractSyntaxTree ast) throws CelEvaluationException {
return toRuntimeProgram(planner().plan(ast));
@@ -271,6 +274,9 @@ public abstract static class Builder implements CelRuntimeBuilder {
@Override
public abstract Builder setExtensionRegistry(ExtensionRegistry extensionRegistry);
+ @Override
+ public abstract Builder setRuntimeEquality(RuntimeEquality runtimeEquality);
+
@Override
public abstract Builder setTypeProvider(CelTypeProvider celTypeProvider);
@@ -292,6 +298,8 @@ public abstract static class Builder implements CelRuntimeBuilder {
abstract ExtensionRegistry extensionRegistry();
+ abstract RuntimeEquality runtimeEquality();
+
abstract ImmutableMap functionBindings();
abstract ImmutableSet.Builder fileDescriptorsBuilder();
@@ -443,6 +451,7 @@ private static DefaultDispatcher newDispatcher(
binding.getOverloadId(),
binding.getArgTypes(),
binding.isStrict(),
+ binding.getNullabilityProperties(),
binding.getDefinition());
}
@@ -456,6 +465,7 @@ private static DefaultDispatcher newDispatcher(
binding.getOverloadId(),
binding.getArgTypes(),
binding.isStrict(),
+ binding.getNullabilityProperties(),
binding.getDefinition());
}
@@ -487,7 +497,14 @@ public CelRuntime build() {
DynamicProto dynamicProto = DynamicProto.create(defaultMessageFactory);
CelValueProvider protoMessageValueProvider =
ProtoMessageValueProvider.newInstance(options(), dynamicProto);
- RuntimeEquality runtimeEquality = ProtoMessageRuntimeEquality.create(dynamicProto, options());
+
+ RuntimeEquality runtimeEquality;
+ if (runtimeEquality() == null) {
+ runtimeEquality = ProtoMessageRuntimeEquality.create(dynamicProto, options());
+ } else {
+ runtimeEquality = runtimeEquality();
+ }
+
ImmutableSet runtimeLibraries = runtimeLibrariesBuilder().build();
// Add libraries, such as extensions
for (CelRuntimeLibrary celLibrary : runtimeLibraries) {
diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java
index b9ce022cf..fbb10d2d6 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java
@@ -72,6 +72,8 @@ public final class CelRuntimeLegacyImpl implements CelRuntime {
// CEL-Internal-4
private final ExtensionRegistry extensionRegistry;
+ private final RuntimeEquality runtimeEquality;
+
// A user-provided custom type factory should presumably be thread-safe. This is documented, but
// not enforced.
// CEL-Internal-4
@@ -101,6 +103,7 @@ public CelRuntimeBuilder toRuntimeBuilder() {
// CEL-Internal-2
.setStandardEnvironmentEnabled(standardEnvironmentEnabled)
.setExtensionRegistry(extensionRegistry)
+ .setRuntimeEquality(runtimeEquality)
.addFileTypes(fileDescriptors)
.addLibraries(celRuntimeLibraries)
.addFunctionBindings(celFunctionBindings);
@@ -143,6 +146,8 @@ public static final class Builder implements CelRuntimeBuilder {
private ExtensionRegistry extensionRegistry;
+ private RuntimeEquality runtimeEquality;
+
private boolean standardEnvironmentEnabled;
@Override
@@ -251,6 +256,13 @@ public CelRuntimeBuilder setExtensionRegistry(ExtensionRegistry extensionRegistr
return this;
}
+ @Override
+ public CelRuntimeBuilder setRuntimeEquality(RuntimeEquality runtimeEquality) {
+ checkNotNull(runtimeEquality);
+ this.runtimeEquality = runtimeEquality;
+ return this;
+ }
+
@Override
public CelRuntimeBuilder setContainer(CelContainer container) {
throw new UnsupportedOperationException(
@@ -290,7 +302,13 @@ public CelRuntimeLegacyImpl build() {
runtimeTypeFactory, DefaultMessageFactory.create(celDescriptorPool));
DynamicProto dynamicProto = DynamicProto.create(runtimeTypeFactory);
- RuntimeEquality runtimeEquality = ProtoMessageRuntimeEquality.create(dynamicProto, options);
+ RuntimeEquality runtimeEquality;
+ if (this.runtimeEquality == null) {
+ runtimeEquality = ProtoMessageRuntimeEquality.create(dynamicProto, options);
+ } else {
+ runtimeEquality = this.runtimeEquality;
+ }
+// RuntimeEquality runtimeEquality = ProtoMessageRuntimeEquality.create(dynamicProto, options);
ImmutableSet runtimeLibraries = celRuntimeLibraries.build();
// Add libraries, such as extensions
@@ -315,6 +333,7 @@ public CelRuntimeLegacyImpl build() {
standardFunctionBinding.getOverloadId(),
standardFunctionBinding.getArgTypes(),
standardFunctionBinding.isStrict(),
+ standardFunctionBinding.getNullabilityProperties(),
standardFunctionBinding.getDefinition());
}
@@ -328,6 +347,7 @@ public CelRuntimeLegacyImpl build() {
customBinding.getOverloadId(),
customBinding.getArgTypes(),
customBinding.isStrict(),
+ customBinding.getNullabilityProperties(),
customBinding.getDefinition());
}
@@ -362,6 +382,7 @@ public CelRuntimeLegacyImpl build() {
options,
standardEnvironmentEnabled,
extensionRegistry,
+ runtimeEquality,
customTypeFactory,
overriddenStandardFunctions,
celValueProvider,
@@ -450,6 +471,7 @@ private CelRuntimeLegacyImpl(
CelOptions options,
boolean standardEnvironmentEnabled,
ExtensionRegistry extensionRegistry,
+ RuntimeEquality runtimeEquality,
@Nullable Function customTypeFactory,
@Nullable CelStandardFunctions overriddenStandardFunctions,
@Nullable CelValueProvider celValueProvider,
@@ -460,6 +482,7 @@ private CelRuntimeLegacyImpl(
this.options = options;
this.standardEnvironmentEnabled = standardEnvironmentEnabled;
this.extensionRegistry = extensionRegistry;
+ this.runtimeEquality = runtimeEquality;
this.customTypeFactory = customTypeFactory;
this.overriddenStandardFunctions = overriddenStandardFunctions;
this.celValueProvider = celValueProvider;
diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java
index 0a467db81..55c1b0f55 100644
--- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java
+++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java
@@ -1,4 +1,5 @@
// Copyright 2022 Google LLC
+// Portions Copyright 2026 Evolveum
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -18,7 +19,6 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.auto.value.AutoValue;
-import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -33,6 +33,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
/**
* Default implementation of dispatcher.
@@ -71,7 +72,7 @@ static Optional findOverloadMatchingArgs(
throws CelEvaluationException {
int matchingOverloadCount = 0;
CelResolvedOverload match = null;
- List candidates = null;
+ List candidates = null;
for (String overloadId : overloadIds) {
CelResolvedOverload overload = overloads.get(overloadId);
// If the overload is null, it means that the function was not registered; however, it is
@@ -80,24 +81,70 @@ static Optional findOverloadMatchingArgs(
if (++matchingOverloadCount > 1) {
if (candidates == null) {
candidates = new ArrayList<>();
- candidates.add(match.getOverloadId());
+ candidates.add(match);
}
- candidates.add(overloadId);
+ candidates.add(overload);
}
match = overload;
}
}
if (matchingOverloadCount > 1) {
- throw CelEvaluationExceptionBuilder.newBuilder(
- "Ambiguous overloads for function '%s'. Matching candidates: %s",
- functionName, Joiner.on(", ").join(candidates))
- .setErrorCode(CelErrorCode.AMBIGUOUS_OVERLOAD)
- .build();
+ if (isCaseOfNullOverload(args, candidates)) {
+ return Optional.of(determineNullOverloadOrThrow(functionName, args, candidates));
+ } else {
+ throw CelEvaluationExceptionBuilder.newBuilder(
+ "Ambiguous overloads for function '%s'. Matching candidates: %s",
+ functionName,
+ candidates
+ .stream()
+ .map(CelResolvedOverload::getOverloadId)
+ .collect(Collectors.joining(", "))
+ )
+ .setErrorCode(CelErrorCode.AMBIGUOUS_OVERLOAD)
+ .build();
+ }
}
return Optional.ofNullable(match);
}
+ private static boolean isCaseOfNullOverload(Object[] args, List candidates) {
+ if (args.length < 1) {
+ return false;
+ }
+ return CelFunctionOverload.isNullEquivalent(args[0]);
+ }
+
+ private static CelResolvedOverload determineNullOverloadOrThrow(
+ String functionName,
+ Object[] args,
+ List candidates)
+ throws CelEvaluationException {
+ CelResolvedOverload matchedOverload = null;
+ for (CelResolvedOverload candidate : candidates) {
+ if (!candidate.getNullabilityProperties().isNullable()) {
+ continue;
+ }
+ if (matchedOverload == null) {
+ matchedOverload = candidate;
+ } else {
+ if (!candidate.getNullabilityProperties().equals(matchedOverload.getNullabilityProperties())) {
+ throw CelEvaluationExceptionBuilder.newBuilder(
+ "Ambiguous overloads for function '%s' due to mismatch in nullability properties. Matching candidates: %s",
+ functionName,
+ candidates
+ .stream()
+ .map(CelResolvedOverload::getOverloadId)
+ .collect(Collectors.joining(", "))
+ )
+ .setErrorCode(CelErrorCode.AMBIGUOUS_OVERLOAD)
+ .build();
+ }
+ }
+ }
+ return CelResolvedNullOverload.of(matchedOverload.getParameterTypes(), matchedOverload.getNullabilityProperties());
+ }
+
/**
* Finds the single registered overload iff it's marked as a non-strict function.
*
@@ -140,15 +187,18 @@ abstract static class OverloadEntry {
abstract boolean isStrict();
+ abstract NullabilityProperties nullabilityProperties();
+
abstract CelFunctionOverload overload();
private static OverloadEntry of(
String functionName,
ImmutableList> argTypes,
boolean isStrict,
+ NullabilityProperties nullabilityProperties,
CelFunctionOverload overload) {
return new AutoValue_DefaultDispatcher_Builder_OverloadEntry(
- functionName, argTypes, isStrict, overload);
+ functionName, argTypes, isStrict, nullabilityProperties, overload);
}
}
@@ -160,6 +210,7 @@ public Builder addOverload(
String overloadId,
ImmutableList> argTypes,
boolean isStrict,
+ NullabilityProperties nullabilityProperties,
CelFunctionOverload overload) {
checkNotNull(functionName);
checkArgument(!functionName.isEmpty(), "Function name cannot be empty.");
@@ -168,7 +219,7 @@ public Builder addOverload(
checkNotNull(argTypes);
checkNotNull(overload);
- OverloadEntry newEntry = OverloadEntry.of(functionName, argTypes, isStrict, overload);
+ OverloadEntry newEntry = OverloadEntry.of(functionName, argTypes, isStrict, nullabilityProperties, overload);
overloads.merge(
overloadId,
@@ -196,8 +247,14 @@ private OverloadEntry mergeDynamicDispatchesOrThrow(
boolean isStrict =
mergedOverload.getOverloadBindings().stream().allMatch(CelFunctionBinding::isStrict);
+ if (mergedOverload.getOverloadBindings().stream().map(CelFunctionBinding::getNullabilityProperties).distinct().count() > 1) {
+ throw new IllegalArgumentException("Cannot merge overload '" + overloadId + "' due to mismatch in nullability properties");
+ }
+ NullabilityProperties nullabilityProperties = CelFunctionBinding.mergeNullabilityProperties(
+ mergedOverload.getOverloadBindings().stream()
+ .map(CelFunctionBinding::getNullabilityProperties).collect(Collectors.toList()));
- return OverloadEntry.of(overloadId, incoming.argTypes(), isStrict, mergedOverload);
+ return OverloadEntry.of(overloadId, incoming.argTypes(), isStrict, nullabilityProperties, mergedOverload);
}
throw new IllegalArgumentException("Duplicate overload ID binding: " + overloadId);
@@ -217,6 +274,7 @@ public DefaultDispatcher build() {
overloadId,
overloadImpl,
overloadEntry.isStrict(),
+ overloadEntry.nullabilityProperties(),
overloadEntry.argTypes()));
}
diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java
index fdab71c3d..7b0a72a3e 100644
--- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java
+++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java
@@ -504,7 +504,23 @@ private IntermediateResult dispatchCall(
CelResolvedOverload overload =
findOverloadOrThrow(frame, expr, callExpr.function(), overloadIds, argArray);
try {
- Object dispatchResult = overload.getDefinition().apply(argArray);
+ Object dispatchResult;
+ if (celOptions.enableEasyNull()) {
+ Object[] nullableArgArray = Arrays.stream(argArray).map(
+ arg -> arg instanceof dev.cel.common.values.NullValue ? null : arg
+ ).toArray();
+ if (argArray.length > 0 && nullableArgArray[0] == null
+ && overload.getNullabilityProperties() != null && overload.getNullabilityProperties().getDefaultFunction() != null) {
+ dispatchResult = overload.getNullabilityProperties().getDefaultFunction().apply(nullableArgArray);
+ } else {
+ dispatchResult = overload.getDefinition().apply(nullableArgArray);
+ }
+ if (dispatchResult == null) {
+ dispatchResult = dev.cel.common.values.NullValue.NULL_VALUE;
+ }
+ } else {
+ dispatchResult = overload.getDefinition().apply(argArray);
+ }
// CustomFunctions themselves can return a CelUnknownSet directly.
dispatchResult = InterpreterUtil.maybeAdaptToAccumulatedUnknowns(dispatchResult);
if (celOptions.unwrapWellKnownTypesOnFunctionDispatch()) {
@@ -1003,6 +1019,8 @@ private IntermediateResult evalComprehension(
iterRange = (List