2323import com .google .common .collect .ImmutableList ;
2424import com .google .common .collect .ImmutableSet ;
2525import com .google .errorprone .annotations .CanIgnoreReturnValue ;
26+ import com .google .protobuf .Descriptors .FieldDescriptor ;
27+ import com .google .protobuf .Message ;
2628import dev .cel .bundle .Cel ;
2729import dev .cel .common .CelAbstractSyntaxTree ;
2830import dev .cel .common .CelMutableAst ;
4244import dev .cel .common .navigation .CelNavigableMutableAst ;
4345import dev .cel .common .navigation .CelNavigableMutableExpr ;
4446import dev .cel .common .navigation .TraversalOrder ;
47+ import dev .cel .common .types .CelType ;
48+ import dev .cel .common .types .CelTypeProvider ;
4549import dev .cel .common .types .SimpleType ;
50+ import dev .cel .common .types .StructType ;
4651import dev .cel .extensions .CelOptionalLibrary .Function ;
4752import dev .cel .optimizer .AstMutator ;
4853import dev .cel .optimizer .CelAstOptimizer ;
@@ -276,6 +281,38 @@ private static boolean areChildrenArgConstant(CelNavigableMutableExpr expr) {
276281 return false ;
277282 }
278283
284+ /**
285+ * Checks whether the provided identifier node is a loop variable bound by a surrounding
286+ * comprehension. Identifiers existing strictly within loop initialization bounds (e.g. iterRange)
287+ * are not considered loop variables.
288+ */
289+ private static boolean isComprehensionBoundLoopVariable (CelNavigableMutableExpr identNode ) {
290+ String identName = identNode .expr ().ident ().name ();
291+ CelNavigableMutableExpr child = identNode ;
292+ CelNavigableMutableExpr parent = identNode .parent ().orElse (null );
293+
294+ while (parent != null ) {
295+ if (parent .getKind ().equals (Kind .COMPREHENSION )) {
296+ CelMutableComprehension comprehension = parent .expr ().comprehension ();
297+ long childId = child .expr ().id ();
298+
299+ boolean isInitializationNode =
300+ childId == comprehension .iterRange ().id () || childId == comprehension .accuInit ().id ();
301+
302+ if (!isInitializationNode ) {
303+ if (identName .equals (comprehension .accuVar ())
304+ || identName .equals (comprehension .iterVar ())
305+ || identName .equals (comprehension .iterVar2 ())) {
306+ return true ;
307+ }
308+ }
309+ }
310+ child = parent ;
311+ parent = parent .parent ().orElse (null );
312+ }
313+ return false ;
314+ }
315+
279316 private static boolean isNestedComprehension (CelNavigableMutableExpr expr ) {
280317 Optional <CelNavigableMutableExpr > maybeParent = expr .parent ();
281318 while (maybeParent .isPresent ()) {
@@ -305,25 +342,28 @@ private Optional<CelMutableAst> maybeFold(
305342 // ex2: optional.ofNonZeroValue(5) -> optional.of(5)
306343 if (result instanceof Optional <?>) {
307344 Optional <?> optResult = ((Optional <?>) result );
308- return maybeRewriteOptional (optResult , mutableAst , node .expr ());
345+ return maybeRewriteOptional (cel , optResult , mutableAst , node .expr ());
309346 }
310347
311- CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult (result ).orElse (null );
348+ CelMutableExpr adaptedResult =
349+ maybeAdaptEvaluatedResult (cel .getTypeProvider (), result ).orElse (null );
312350 if (adaptedResult == null ) {
313351 return Optional .empty ();
314352 }
315353
316354 return Optional .of (astMutator .replaceSubtree (mutableAst , adaptedResult , node .id ()));
317355 }
318356
319- private Optional <CelMutableExpr > maybeAdaptEvaluatedResult (Object result ) {
357+ private Optional <CelMutableExpr > maybeAdaptEvaluatedResult (
358+ CelTypeProvider typeProvider , Object result ) {
320359 if (CelConstant .isConstantValue (result )) {
321360 return Optional .of (CelMutableExpr .ofConstant (CelConstant .ofObjectValue (result )));
322361 } else if (result instanceof Collection <?>) {
323362 Collection <?> collection = (Collection <?>) result ;
324363 List <CelMutableExpr > listElements = new ArrayList <>();
325364 for (Object evaluatedElement : collection ) {
326- CelMutableExpr adaptedExpr = maybeAdaptEvaluatedResult (evaluatedElement ).orElse (null );
365+ CelMutableExpr adaptedExpr =
366+ maybeAdaptEvaluatedResult (typeProvider , evaluatedElement ).orElse (null );
327367 if (adaptedExpr == null ) {
328368 return Optional .empty ();
329369 }
@@ -335,11 +375,13 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
335375 Map <?, ?> map = (Map <?, ?>) result ;
336376 List <CelMutableMap .Entry > mapEntries = new ArrayList <>();
337377 for (Map .Entry <?, ?> entry : map .entrySet ()) {
338- CelMutableExpr adaptedKey = maybeAdaptEvaluatedResult (entry .getKey ()).orElse (null );
378+ CelMutableExpr adaptedKey =
379+ maybeAdaptEvaluatedResult (typeProvider , entry .getKey ()).orElse (null );
339380 if (adaptedKey == null ) {
340381 return Optional .empty ();
341382 }
342- CelMutableExpr adaptedValue = maybeAdaptEvaluatedResult (entry .getValue ()).orElse (null );
383+ CelMutableExpr adaptedValue =
384+ maybeAdaptEvaluatedResult (typeProvider , entry .getValue ()).orElse (null );
343385 if (adaptedValue == null ) {
344386 return Optional .empty ();
345387 }
@@ -364,14 +406,40 @@ private Optional<CelMutableExpr> maybeAdaptEvaluatedResult(Object result) {
364406 CelMutableExpr .ofConstant (CelConstant .ofValue (timestampStrArg )));
365407
366408 return Optional .of (CelMutableExpr .ofCall (timestampCall ));
409+ } else if (result instanceof Message ) {
410+ Message message = (Message ) result ;
411+ Optional <CelType > typeOpt =
412+ typeProvider .findType (message .getDescriptorForType ().getFullName ());
413+ if (!typeOpt .isPresent () || !(typeOpt .get () instanceof StructType )) {
414+ return Optional .empty ();
415+ }
416+ StructType structType = (StructType ) typeOpt .get ();
417+ List <CelMutableStruct .Entry > structEntries = new ArrayList <>();
418+ for (String fieldName : structType .fieldNames ()) {
419+ FieldDescriptor fieldDescriptor = message .getDescriptorForType ().findFieldByName (fieldName );
420+ if (fieldDescriptor == null ) {
421+ continue ;
422+ }
423+ if (!message .getAllFields ().containsKey (fieldDescriptor )) {
424+ continue ;
425+ }
426+ CelMutableExpr adaptedFieldExpr =
427+ maybeAdaptEvaluatedResult (typeProvider , message .getField (fieldDescriptor )).orElse (null );
428+ if (adaptedFieldExpr == null ) {
429+ return Optional .empty ();
430+ }
431+ structEntries .add (CelMutableStruct .Entry .create (0 , fieldName , adaptedFieldExpr ));
432+ }
433+ return Optional .of (
434+ CelMutableExpr .ofStruct (CelMutableStruct .create (structType .name (), structEntries )));
367435 }
368436
369437 // Evaluated result cannot be folded (e.g: unknowns)
370438 return Optional .empty ();
371439 }
372440
373441 private Optional <CelMutableAst > maybeRewriteOptional (
374- Optional <?> optResult , CelMutableAst mutableAst , CelMutableExpr expr ) {
442+ Cel cel , Optional <?> optResult , CelMutableAst mutableAst , CelMutableExpr expr ) {
375443 Object unwrappedResult = optResult .orElse (null );
376444 if (unwrappedResult == null ) {
377445 if (isCallToFunction (expr , Function .OPTIONAL_NONE .getFunction ())) {
@@ -387,7 +455,8 @@ private Optional<CelMutableAst> maybeRewriteOptional(
387455 return Optional .empty ();
388456 }
389457
390- CelMutableExpr adaptedResult = maybeAdaptEvaluatedResult (unwrappedResult ).orElse (null );
458+ CelMutableExpr adaptedResult =
459+ maybeAdaptEvaluatedResult (cel .getTypeProvider (), unwrappedResult ).orElse (null );
391460 if (adaptedResult == null ) {
392461 // Evaluated result is not an adaptable constant. Leave the optional as is.
393462 return Optional .empty ();
@@ -733,6 +802,7 @@ private static Object evaluateExpr(Cel cel, CelNavigableMutableExpr navigableMut
733802 navigableMutableExpr
734803 .allNodes ()
735804 .filter (node -> node .getKind ().equals (Kind .IDENT ))
805+ .filter (node -> !isComprehensionBoundLoopVariable (node ))
736806 .map (node -> node .expr ().ident ().name ())
737807 .map (CelAttributePattern ::fromQualifiedIdentifier )
738808 .collect (toImmutableList ());
0 commit comments