2424import com .google .common .collect .ImmutableSet ;
2525import com .google .errorprone .annotations .CanIgnoreReturnValue ;
2626import dev .cel .bundle .Cel ;
27+ import dev .cel .bundle .CelBuilder ;
2728import dev .cel .common .CelAbstractSyntaxTree ;
2829import dev .cel .common .CelMutableAst ;
2930import dev .cel .common .CelSource ;
4243import dev .cel .common .navigation .CelNavigableMutableAst ;
4344import dev .cel .common .navigation .CelNavigableMutableExpr ;
4445import dev .cel .common .navigation .TraversalOrder ;
46+ import dev .cel .common .types .CelType ;
47+ import dev .cel .common .types .CelTypeProvider ;
4548import dev .cel .common .types .SimpleType ;
49+ import dev .cel .common .types .StructType ;
50+ import dev .cel .common .values .CelValue ;
51+ import dev .cel .common .values .CelValueProvider ;
52+ import dev .cel .common .values .StructValue ;
4653import dev .cel .extensions .CelOptionalLibrary .Function ;
4754import dev .cel .optimizer .AstMutator ;
4855import dev .cel .optimizer .CelAstOptimizer ;
5966import java .util .List ;
6067import java .util .Map ;
6168import java .util .Optional ;
69+ import org .jspecify .annotations .Nullable ;
6270
6371/**
6472 * Performs optimization for inlining constant scalar and aggregate literal values within function
@@ -95,8 +103,16 @@ private static CelMutableExpr newOptionalNoneExpr() {
95103 @ Override
96104 public OptimizationResult optimize (CelAbstractSyntaxTree ast , Cel cel )
97105 throws CelOptimizationException {
106+ CelBuilder builder = cel .toCelBuilder ();
107+ CelValueProvider valueProvider ;
108+ try {
109+ valueProvider = builder .getValueProvider ();
110+ } catch (UnsupportedOperationException e ) {
111+ // Legacy runtime does not support getValueProvider and may throw.
112+ valueProvider = null ;
113+ }
98114 // Override the environment's expected type to generally allow all subtrees to be folded.
99- Cel optimizerEnv = cel . toCelBuilder () .setResultType (SimpleType .DYN ).build ();
115+ Cel optimizerEnv = builder .setResultType (SimpleType .DYN ).build ();
100116
101117 CelMutableAst mutableAst = CelMutableAst .fromCelAst (ast );
102118 int iterCount = 0 ;
@@ -123,7 +139,7 @@ public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel)
123139 if (!mutatedResult .isPresent ()) {
124140 // Evaluate the call then fold
125141 try {
126- mutatedResult = maybeFold (optimizerEnv , mutableAst , foldableExpr );
142+ mutatedResult = maybeFold (optimizerEnv , valueProvider , mutableAst , foldableExpr );
127143 } catch (CelEvaluationException e ) {
128144 throw new CelOptimizationException (
129145 "Constant folding failure. Failed to evaluate subtree due to: " + e .getMessage (),
@@ -290,7 +306,10 @@ private static boolean isNestedComprehension(CelNavigableMutableExpr expr) {
290306 }
291307
292308 private Optional <CelMutableAst > maybeFold (
293- Cel cel , CelMutableAst mutableAst , CelNavigableMutableExpr node )
309+ Cel cel ,
310+ CelValueProvider valueProvider ,
311+ CelMutableAst mutableAst ,
312+ CelNavigableMutableExpr node )
294313 throws CelOptimizationException , CelEvaluationException {
295314 Object result ;
296315 try {
@@ -305,25 +324,33 @@ private Optional<CelMutableAst> maybeFold(
305324 // ex2: optional.ofNonZeroValue(5) -> optional.of(5)
306325 if (result instanceof Optional <?>) {
307326 Optional <?> optResult = ((Optional <?>) result );
308- return maybeRewriteOptional (optResult , mutableAst , node .expr ());
327+ return maybeRewriteOptional (
328+ cel .getTypeProvider (), valueProvider , optResult , mutableAst , node .expr ());
309329 }
310330
311- CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult (result ).orElse (null );
331+ CelMutableExpr adaptedResult =
332+ maybeAdaptEvaluatedResult (cel .getTypeProvider (), valueProvider , result ).orElse (null );
312333 if (adaptedResult == null ) {
313334 return Optional .empty ();
314335 }
315336
316337 return Optional .of (astMutator .replaceSubtree (mutableAst , adaptedResult , node .id ()));
317338 }
318339
319- private Optional <CelMutableExpr > maybeAdaptEvaluatedResult (Object result ) {
340+ private Optional <CelMutableExpr > maybeAdaptEvaluatedResult (
341+ CelTypeProvider typeProvider , @ Nullable CelValueProvider valueProvider , Object result ) {
342+ if (valueProvider != null && !(result instanceof CelValue )) {
343+ result = valueProvider .celValueConverter ().toRuntimeValue (result );
344+ }
345+
320346 if (CelConstant .isConstantValue (result )) {
321347 return Optional .of (CelMutableExpr .ofConstant (CelConstant .ofObjectValue (result )));
322348 } else if (result instanceof Collection <?>) {
323349 Collection <?> collection = (Collection <?>) result ;
324350 List <CelMutableExpr > listElements = new ArrayList <>();
325351 for (Object evaluatedElement : collection ) {
326- CelMutableExpr adaptedExpr = maybeAdaptEvaluatedResult (evaluatedElement ).orElse (null );
352+ CelMutableExpr adaptedExpr =
353+ maybeAdaptEvaluatedResult (typeProvider , valueProvider , evaluatedElement ).orElse (null );
327354 if (adaptedExpr == null ) {
328355 return Optional .empty ();
329356 }
@@ -335,11 +362,13 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
335362 Map <?, ?> map = (Map <?, ?>) result ;
336363 List <CelMutableMap .Entry > mapEntries = new ArrayList <>();
337364 for (Map .Entry <?, ?> entry : map .entrySet ()) {
338- CelMutableExpr adaptedKey = maybeAdaptEvaluatedResult (entry .getKey ()).orElse (null );
365+ CelMutableExpr adaptedKey =
366+ maybeAdaptEvaluatedResult (typeProvider , valueProvider , entry .getKey ()).orElse (null );
339367 if (adaptedKey == null ) {
340368 return Optional .empty ();
341369 }
342- CelMutableExpr adaptedValue = maybeAdaptEvaluatedResult (entry .getValue ()).orElse (null );
370+ CelMutableExpr adaptedValue =
371+ maybeAdaptEvaluatedResult (typeProvider , valueProvider , entry .getValue ()).orElse (null );
343372 if (adaptedValue == null ) {
344373 return Optional .empty ();
345374 }
@@ -364,14 +393,43 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
364393 CelMutableExpr .ofConstant (CelConstant .ofValue (timestampStrArg )));
365394
366395 return Optional .of (CelMutableExpr .ofCall (timestampCall ));
396+ } else if (result instanceof StructValue ) {
397+ @ SuppressWarnings ("unchecked" ) // Unchecked: StructValue only supports String keys.
398+ StructValue <String , ?> structValue = (StructValue <String , ?>) result ;
399+ List <CelMutableStruct .Entry > structEntries = new ArrayList <>();
400+
401+ String typeName = structValue .celType ().name ();
402+ CelType optType = typeProvider .findType (typeName ).orElse (null );
403+ if (!(optType instanceof StructType )) {
404+ return Optional .empty ();
405+ }
406+ StructType structType = (StructType ) optType ;
407+ for (String fieldName : structType .fieldNames ()) {
408+ Optional <?> fieldOpt = structValue .find (fieldName );
409+ if (!fieldOpt .isPresent ()) {
410+ continue ;
411+ }
412+ CelMutableExpr adaptedFieldExpr =
413+ maybeAdaptEvaluatedResult (typeProvider , valueProvider , fieldOpt .get ()).orElse (null );
414+ if (adaptedFieldExpr == null ) {
415+ return Optional .empty ();
416+ }
417+ structEntries .add (CelMutableStruct .Entry .create (0 , fieldName , adaptedFieldExpr ));
418+ }
419+ return Optional .of (
420+ CelMutableExpr .ofStruct (CelMutableStruct .create (structType .name (), structEntries )));
367421 }
368422
369423 // Evaluated result cannot be folded (e.g: unknowns)
370424 return Optional .empty ();
371425 }
372426
373427 private Optional <CelMutableAst > maybeRewriteOptional (
374- Optional <?> optResult , CelMutableAst mutableAst , CelMutableExpr expr ) {
428+ CelTypeProvider typeProvider ,
429+ CelValueProvider valueProvider ,
430+ Optional <?> optResult ,
431+ CelMutableAst mutableAst ,
432+ CelMutableExpr expr ) {
375433 Object unwrappedResult = optResult .orElse (null );
376434 if (unwrappedResult == null ) {
377435 if (isCallToFunction (expr , Function .OPTIONAL_NONE .getFunction ())) {
@@ -387,7 +445,8 @@ private Optional<CelMutableAst> maybeRewriteOptional(
387445 return Optional .empty ();
388446 }
389447
390- CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult (unwrappedResult ).orElse (null );
448+ CelMutableExpr adaptedResult =
449+ maybeAdaptEvaluatedResult (typeProvider , valueProvider , unwrappedResult ).orElse (null );
391450 if (adaptedResult == null ) {
392451 // Evaluated result is not an adaptable constant. Leave the optional as is.
393452 return Optional .empty ();
0 commit comments