From 4904a68d31cf69d30e08c1f735b00dec3cdd2356 Mon Sep 17 00:00:00 2001 From: CEL Dev Team Date: Fri, 4 Sep 2026 02:34:48 -0700 Subject: [PATCH] Avoid re-checking the AST in CelOptimizer if it was not modified. PiperOrigin-RevId: 976199136 --- .../dev/cel/optimizer/CelOptimizerImpl.java | 21 +++++++++------- .../SubexpressionOptimizerTest.java | 25 +++++++++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/optimizer/src/main/java/dev/cel/optimizer/CelOptimizerImpl.java b/optimizer/src/main/java/dev/cel/optimizer/CelOptimizerImpl.java index f5e30093a..bf42b2e60 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/CelOptimizerImpl.java +++ b/optimizer/src/main/java/dev/cel/optimizer/CelOptimizerImpl.java @@ -39,6 +39,7 @@ final class CelOptimizerImpl implements CelOptimizer { } @Override + @SuppressWarnings("ReferenceEquality") public CelAbstractSyntaxTree optimize(CelAbstractSyntaxTree ast) throws CelOptimizationException { if (!ast.isChecked()) { throw new IllegalArgumentException("AST must be type-checked."); @@ -49,16 +50,18 @@ public CelAbstractSyntaxTree optimize(CelAbstractSyntaxTree ast) throws CelOptim try { for (CelAstOptimizer optimizer : astOptimizers) { OptimizationResult result = optimizer.optimize(optimizedAst, celOptimizerEnv); - if (!result.newFunctionDecls().isEmpty() || !result.newVarDecls().isEmpty()) { - celOptimizerEnv = - celOptimizerEnv - .toCelBuilder() - .addVarDeclarations(result.newVarDecls()) - .addFunctionDeclarations(result.newFunctionDecls()) - .build(); + if (result.optimizedAst() != optimizedAst) { + if (!result.newFunctionDecls().isEmpty() || !result.newVarDecls().isEmpty()) { + celOptimizerEnv = + celOptimizerEnv + .toCelBuilder() + .addVarDeclarations(result.newVarDecls()) + .addFunctionDeclarations(result.newFunctionDecls()) + .build(); + } + optimizedAst = celOptimizerEnv.check(result.optimizedAst()).getAst(); + assertAstIdCorrectness(optimizedAst); } - optimizedAst = celOptimizerEnv.check(result.optimizedAst()).getAst(); - assertAstIdCorrectness(optimizedAst); } } catch (CelValidationException e) { throw new CelOptimizationException( diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java index 1a36bd16b..d249c4bc6 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java @@ -702,6 +702,31 @@ public void block_lazyEvaluationContainsError_cleansUpCycleState() throws Except assertThat(e).hasMessageThat().doesNotContain("Cycle detected"); } + @Test + public void cse_nestedMacro_noOp_assertAstIdCorrectness() throws Exception { + Cel cel = + runtimeFlavor + .builder() + .addVar("x", SimpleType.DYN) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .setOptions(CelOptions.current().populateMacroCalls(true).build()) + .addCompilerLibraries(CelExtensions.comprehensions()) + .addRuntimeLibraries(CelExtensions.comprehensions()) + .build(); + CelOptimizer celOptimizer = + CelOptimizerFactory.standardCelOptimizerBuilder(cel) + .addAstOptimizers(SubexpressionOptimizer.getInstance()) + .build(); + CelAbstractSyntaxTree ast = + cel.compile("[{}, {\"a\": 1}, {\"b\": 2}].filter(m, has(x.a))").getAst(); + + CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast); + + assertThat(CEL_UNPARSER.unparse(optimizedAst)) + .isEqualTo("[{}, {\"a\": 1}, {\"b\": 2}].filter(m, has(x.a))"); + assertThat(optimizedAst).isSameInstanceAs(ast); + } + /** * Converts AST containing cel.block related test functions to internal functions (e.g: cel.block * -> cel.@block)