Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions optimizer/optimizers/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ java_library(
name = "common_subexpression_elimination",
exports = ["//optimizer/src/main/java/dev/cel/optimizer/optimizers:common_subexpression_elimination"],
)

java_library(
name = "inlining",
exports = ["//optimizer/src/main/java/dev/cel/optimizer/optimizers:inlining"],
)
27 changes: 27 additions & 0 deletions optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,33 @@ java_library(
],
)

java_library(
name = "inlining",
srcs = [
"InliningOptimizer.java",
],
tags = [
],
deps = [
"//:auto_value",
"//bundle:cel",
"//common:cel_ast",
"//common:mutable_ast",
"//common:operator",
"//common/ast",
"//common/ast:mutable_expr",
"//common/navigation:mutable_navigation",
"//common/types",
"//common/types:type_providers",
"//common/values",
"//optimizer:ast_optimizer",
"//optimizer:mutable_ast",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:org_jspecify_jspecify",
],
)

java_library(
name = "default_optimizer_constants",
srcs = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// Copyright 2026 Google LLC
//
// 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.optimizer.optimizers;

import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import dev.cel.bundle.Cel;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelMutableAst;
import dev.cel.common.Operator;
import dev.cel.common.ast.CelConstant;
import dev.cel.common.ast.CelExpr.ExprKind.Kind;
import dev.cel.common.ast.CelMutableExpr;
import dev.cel.common.ast.CelMutableExpr.CelMutableCall;
import dev.cel.common.ast.CelMutableExpr.CelMutableComprehension;
import dev.cel.common.navigation.CelNavigableMutableAst;
import dev.cel.common.navigation.CelNavigableMutableExpr;
import dev.cel.common.types.CelKind;
import dev.cel.common.types.CelType;
import dev.cel.common.types.SimpleType;
import dev.cel.common.values.NullValue;
import dev.cel.optimizer.AstMutator;
import dev.cel.optimizer.CelAstOptimizer;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.stream.Stream;

/**
* Performs optimization for inlining variables within function calls and select statements with
* their associated AST.
*/
public final class InliningOptimizer implements CelAstOptimizer {

private final ImmutableList<InlineVariable> inlineVariables;
private final AstMutator astMutator;

public static InliningOptimizer newInstance(InlineVariable... inlineVariables) {
return newInstance(InliningOptions.newBuilder().build(), ImmutableList.copyOf(inlineVariables));
}

public static InliningOptimizer newInstance(
InliningOptions options, InlineVariable... inlineVariables) {
return newInstance(options, ImmutableList.copyOf(inlineVariables));
}

public static InliningOptimizer newInstance(
InliningOptions options, Iterable<InlineVariable> inlineVariables) {
return new InliningOptimizer(options, ImmutableList.copyOf(inlineVariables));
}

@Override
public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) {
CelMutableAst mutableAst = CelMutableAst.fromCelAst(ast);
for (InlineVariable inlineVariable : inlineVariables) {
ImmutableList<CelNavigableMutableExpr> inlinableExprs =
CelNavigableMutableAst.fromAst(mutableAst)
.getRoot()
.allNodes()
.filter(node -> canInline(node, inlineVariable.name()))
.collect(toImmutableList());

for (CelNavigableMutableExpr inlinableExpr : inlinableExprs) {
CelMutableAst inlineVariableAst = CelMutableAst.fromCelAst(inlineVariable.ast());
CelMutableExpr replacementExpr = inlineVariableAst.expr();

if (inlinableExpr.getKind().equals(Kind.SELECT)
&& inlinableExpr.expr().select().testOnly()) {
if (replacementExpr.getKind().equals(Kind.SELECT)) {
// Preserve testOnly property for Select replacements (has(A) -> has(B))
replacementExpr.select().setTestOnly(true);
} else {
CelType replacementType =
inlineVariable
.ast()
.getType(replacementExpr.id())
.orElseThrow(() -> new NoSuchElementException("Type is not present."));
if (isSizerType(replacementType)) {
// has(X) -> X.size() != 0
replacementExpr =
CelMutableExpr.ofCall(
CelMutableCall.create(
Operator.NOT_EQUALS.getFunction(),
CelMutableExpr.ofCall(CelMutableCall.create(replacementExpr, "size")),
CelMutableExpr.ofConstant(CelConstant.ofValue(0))));
} else if (replacementType.isAssignableFrom(SimpleType.NULL_TYPE)) {
// has(X) -> X != null
replacementExpr =
CelMutableExpr.ofCall(
CelMutableCall.create(
Operator.NOT_EQUALS.getFunction(),
replacementExpr,
CelMutableExpr.ofConstant(CelConstant.ofValue(NullValue.NULL_VALUE))));
}
}
}

mutableAst =
astMutator.replaceSubtree(
mutableAst,
CelMutableAst.of(replacementExpr, inlineVariableAst.source()),
inlinableExpr.id());
}
}

return OptimizationResult.create(astMutator.renumberIdsConsecutively(mutableAst).toParsedAst());
}

private static boolean isSizerType(CelType type) {
return type.kind().equals(CelKind.LIST)
|| type.kind().equals(CelKind.MAP)
|| type.equals(SimpleType.STRING)
|| type.equals(SimpleType.BYTES);
}

private static boolean canInline(CelNavigableMutableExpr node, String identifier) {
boolean matches = maybeToQualifiedName(node).map(name -> name.equals(identifier)).orElse(false);

if (!matches) {
return false;
}

for (CelNavigableMutableExpr p = node.parent().orElse(null);
p != null;
p = p.parent().orElse(null)) {
if (p.getKind() != Kind.COMPREHENSION) {
continue;
}

CelMutableComprehension comp = p.expr().comprehension();
boolean shadows =
Stream.of(comp.iterVar(), comp.iterVar2(), comp.accuVar()).anyMatch(identifier::equals);

if (shadows) {
return false;
}
}

return true;
}

private static Optional<String> maybeToQualifiedName(CelNavigableMutableExpr node) {
if (node.getKind().equals(Kind.IDENT)) {
return Optional.of(node.expr().ident().name());
}

if (node.getKind().equals(Kind.SELECT)) {
return node.children()
.findFirst()
.flatMap(InliningOptimizer::maybeToQualifiedName)
.map(operandName -> operandName + "." + node.expr().select().field());
}

return Optional.empty();
}

/** Represents a variable to be inlined. */
@AutoValue
public abstract static class InlineVariable {
public abstract String name();

public abstract CelAbstractSyntaxTree ast();

/**
* Creates a new {@link InlineVariable} with the given name and AST.
*
* <p>The name must be a simple identifier or a qualified name (e.g. "a.b.c") and cannot be an
* internal variable (starting with @).
*/
public static InlineVariable of(String name, CelAbstractSyntaxTree ast) {
if (name.startsWith("@")) {
throw new IllegalArgumentException("Internal variables cannot be inlined: " + name);
}
return new AutoValue_InliningOptimizer_InlineVariable(name, ast);
}
}

/** Options to configure how Inlining behaves. */
@AutoValue
public abstract static class InliningOptions {
public abstract int maxIterationLimit();

/** Builder for configuring the {@link InliningOptimizer.InliningOptions}. */
@AutoValue.Builder
public abstract static class Builder {

/**
* Limit the number of iteration while inlining variables. An exception is thrown if the
* iteration count exceeds the set value.
*/
public abstract InliningOptions.Builder maxIterationLimit(int value);

public abstract InliningOptimizer.InliningOptions build();

Builder() {}
}

/** Returns a new options builder with recommended defaults pre-configured. */
public static InliningOptimizer.InliningOptions.Builder newBuilder() {
return new AutoValue_InliningOptimizer_InliningOptions.Builder().maxIterationLimit(400);
}

InliningOptions() {}
}

private InliningOptimizer(
InliningOptions options, ImmutableList<InlineVariable> inlineVariables) {
this.inlineVariables = inlineVariables;
this.astMutator = AstMutator.newInstance(options.maxIterationLimit());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
* }
* </pre>
*/
public class SubexpressionOptimizer implements CelAstOptimizer {
public final class SubexpressionOptimizer implements CelAstOptimizer {

private static final SubexpressionOptimizer INSTANCE =
new SubexpressionOptimizer(SubexpressionOptimizerOptions.newBuilder().build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ java_library(
"//optimizer:optimizer_builder",
"//optimizer/optimizers:common_subexpression_elimination",
"//optimizer/optimizers:constant_folding",
"//optimizer/optimizers:inlining",
"//parser:macro",
"//parser:unparser",
"//runtime",
Expand Down
Loading
Loading