Skip to content

Commit ad2c6b6

Browse files
l46kokcopybara-github
authored andcommitted
Assign unique indices for mangled comprehension identifiers with different types
PiperOrigin-RevId: 607513056
1 parent 629f85b commit ad2c6b6

6 files changed

Lines changed: 149 additions & 96 deletions

File tree

optimizer/src/main/java/dev/cel/optimizer/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ java_library(
8888
"//common/ast",
8989
"//common/ast:expr_factory",
9090
"//common/navigation",
91+
"//common/types:type_providers",
9192
"@maven//:com_google_errorprone_error_prone_annotations",
9293
"@maven//:com_google_guava_guava",
9394
],

optimizer/src/main/java/dev/cel/optimizer/MutableAst.java

Lines changed: 95 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
import com.google.auto.value.AutoValue;
2222
import com.google.common.annotations.VisibleForTesting;
2323
import com.google.common.base.Preconditions;
24+
import com.google.common.collect.HashBasedTable;
2425
import com.google.common.collect.ImmutableList;
2526
import com.google.common.collect.ImmutableMap;
26-
import com.google.common.collect.ImmutableSet;
27+
import com.google.common.collect.Table;
2728
import com.google.errorprone.annotations.Immutable;
2829
import dev.cel.common.CelAbstractSyntaxTree;
2930
import dev.cel.common.CelSource;
@@ -38,9 +39,13 @@
3839
import dev.cel.common.navigation.CelNavigableAst;
3940
import dev.cel.common.navigation.CelNavigableExpr;
4041
import dev.cel.common.navigation.CelNavigableExpr.TraversalOrder;
42+
import dev.cel.common.types.CelType;
43+
import java.util.HashMap;
44+
import java.util.LinkedHashMap;
4145
import java.util.Map.Entry;
4246
import java.util.NoSuchElementException;
4347
import java.util.Optional;
48+
import java.util.stream.Collectors;
4449

4550
/** MutableAst contains logic for mutating a {@link CelAbstractSyntaxTree}. */
4651
@Immutable
@@ -187,45 +192,112 @@ public CelAbstractSyntaxTree renumberIdsConsecutively(CelAbstractSyntaxTree ast)
187192
*
188193
* <p>The expression IDs are not modified when the identifier names are changed.
189194
*
195+
* <p>Mangling occurs only if the iteration variable is referenced within the loop step.
196+
*
190197
* <p>Iteration variables in comprehensions are numbered based on their comprehension nesting
191-
* levels. Examples:
198+
* levels and the iteration variable's type. Examples:
192199
*
193200
* <ul>
194-
* <li>{@code [true].exists(i, i) && [true].exists(j, j)} -> {@code [true].exists(@c0, @c0) &&
195-
* [true].exists(@c0, @c0)} // Note that i,j gets replaced to the same @c0 in this example
196-
* <li>{@code [true].exists(i, i && [true].exists(j, j))} -> {@code [true].exists(@c0, @c0 &&
197-
* [true].exists(@c1, @c1))}
201+
* <li>{@code [true].exists(i, i) && [true].exists(j, j)} -> {@code [true].exists(@c0:0, @c0:0)
202+
* && [true].exists(@c0:0, @c0:0)} // Note that i,j gets replaced to the same @c0:0 in this
203+
* example as they share the same nesting level and type.
204+
* <li>{@code [1].exists(i, i > 0) && [1u].exists(j, j > 0u)} -> {@code [1].exists(@c0:0, @c0:0
205+
* > 0) && [1u].exists(@c0:1, @c0:1 > 0u)}
206+
* <li>{@code [true].exists(i, i && [true].exists(j, j))} -> {@code [true].exists(@c0:0, @c0:0
207+
* && [true].exists(@c1:0, @c1:0))}
198208
* </ul>
199209
*
200210
* @param ast AST to mutate
201211
* @param newIdentPrefix Prefix to use for new identifier names. For example, providing @c will
202-
* produce @c0, @c1, @c2... as new names.
212+
* produce @c0:0, @c0:1, @c1:0, @c2:0... as new names.
203213
*/
204214
public MangledComprehensionAst mangleComprehensionIdentifierNames(
205215
CelAbstractSyntaxTree ast, String newIdentPrefix) {
206-
int iterCount;
207216
CelNavigableAst newNavigableAst = CelNavigableAst.fromAst(ast);
208-
ImmutableSet.Builder<String> mangledComprehensionIdents = ImmutableSet.builder();
209-
for (iterCount = 0; iterCount < iterationLimit; iterCount++) {
217+
LinkedHashMap<CelNavigableExpr, CelType> comprehensionsToMangle =
218+
newNavigableAst
219+
.getRoot()
220+
// This is important - mangling needs to happen bottom-up to avoid stepping over
221+
// shadowed variables that are not part of the comprehension being mangled.
222+
.allNodes(TraversalOrder.POST_ORDER)
223+
.filter(node -> node.getKind().equals(Kind.COMPREHENSION))
224+
.filter(node -> !node.expr().comprehension().iterVar().startsWith(newIdentPrefix))
225+
.filter(
226+
node -> {
227+
// Ensure the iter_var is actually referenced in the loop_step. If it's not, we
228+
// can skip mangling.
229+
String iterVar = node.expr().comprehension().iterVar();
230+
return CelNavigableExpr.fromExpr(node.expr().comprehension().loopStep())
231+
.allNodes()
232+
.anyMatch(
233+
subNode -> subNode.expr().identOrDefault().name().contains(iterVar));
234+
})
235+
.collect(
236+
Collectors.toMap(
237+
k -> k,
238+
v -> {
239+
String iterVar = v.expr().comprehension().iterVar();
240+
long iterVarId =
241+
CelNavigableExpr.fromExpr(v.expr().comprehension().loopStep())
242+
.allNodes()
243+
.filter(
244+
loopStepNode ->
245+
loopStepNode.expr().identOrDefault().name().equals(iterVar))
246+
.map(CelNavigableExpr::id)
247+
.findAny()
248+
.orElseThrow(
249+
() -> {
250+
throw new NoSuchElementException(
251+
"Expected iteration variable to exist in expr id: "
252+
+ v.id());
253+
});
254+
255+
return ast.getType(iterVarId)
256+
.orElseThrow(
257+
() ->
258+
new NoSuchElementException(
259+
"Checked type not present for: " + iterVarId));
260+
},
261+
(x, y) -> {
262+
throw new IllegalStateException("Unexpected CelNavigableExpr collision");
263+
},
264+
LinkedHashMap::new));
265+
int iterCount = 0;
266+
267+
// The map that we'll eventually return to the caller.
268+
HashMap<String, CelType> mangledIdentNamesToType = new HashMap<>();
269+
// Intermediary table used for the purposes of generating a unique mangled variable name.
270+
Table<Integer, CelType, String> comprehensionLevelToType = HashBasedTable.create();
271+
for (Entry<CelNavigableExpr, CelType> comprehensionEntry : comprehensionsToMangle.entrySet()) {
272+
iterCount++;
273+
// Refetch the comprehension node as mutating the AST could have renumbered its IDs.
210274
CelNavigableExpr comprehensionNode =
211275
newNavigableAst
212276
.getRoot()
213-
// This is important - mangling needs to happen bottom-up to avoid stepping over
214-
// shadowed variables that are not part of the comprehension being mangled.
215277
.allNodes(TraversalOrder.POST_ORDER)
216278
.filter(node -> node.getKind().equals(Kind.COMPREHENSION))
217279
.filter(node -> !node.expr().comprehension().iterVar().startsWith(newIdentPrefix))
218280
.findAny()
219-
.orElse(null);
220-
if (comprehensionNode == null) {
221-
break;
222-
}
281+
.orElseThrow(
282+
() -> new NoSuchElementException("Failed to refetch mutated comprehension"));
283+
CelType comprehensionEntryType = comprehensionEntry.getValue();
223284

224285
CelExpr.Builder comprehensionExpr = comprehensionNode.expr().toBuilder();
225286
String iterVar = comprehensionExpr.comprehension().iterVar();
226287
int comprehensionNestingLevel = countComprehensionNestingLevel(comprehensionNode);
227-
String mangledVarName = newIdentPrefix + comprehensionNestingLevel;
228-
mangledComprehensionIdents.add(mangledVarName);
288+
String mangledVarName;
289+
if (comprehensionLevelToType.contains(comprehensionNestingLevel, comprehensionEntryType)) {
290+
mangledVarName =
291+
comprehensionLevelToType.get(comprehensionNestingLevel, comprehensionEntryType);
292+
} else {
293+
// First time encountering the pair of <ComprehensionLevel, CelType>. Generate a unique
294+
// mangled variable name for this.
295+
int uniqueTypeIdx = comprehensionLevelToType.row(comprehensionNestingLevel).size();
296+
mangledVarName = newIdentPrefix + comprehensionNestingLevel + ":" + uniqueTypeIdx;
297+
comprehensionLevelToType.put(
298+
comprehensionNestingLevel, comprehensionEntryType, mangledVarName);
299+
}
300+
mangledIdentNamesToType.put(mangledVarName, comprehensionEntryType);
229301

230302
CelExpr.Builder mutatedComprehensionExpr =
231303
mangleIdentsInComprehensionExpr(
@@ -254,7 +326,8 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames(
254326
throw new IllegalStateException("Max iteration count reached.");
255327
}
256328

257-
return MangledComprehensionAst.of(newNavigableAst.getAst(), mangledComprehensionIdents.build());
329+
return MangledComprehensionAst.of(
330+
newNavigableAst.getAst(), ImmutableMap.copyOf(mangledIdentNamesToType));
258331
}
259332

260333
/**
@@ -588,11 +661,11 @@ public abstract static class MangledComprehensionAst {
588661
/** AST after the iteration variables have been mangled. */
589662
public abstract CelAbstractSyntaxTree ast();
590663

591-
/** Set of identifiers with the iteration variable mangled. */
592-
public abstract ImmutableSet<String> mangledComprehensionIdents();
664+
/** Map containing the mangled identifier names to their types. */
665+
public abstract ImmutableMap<String, CelType> mangledComprehensionIdents();
593666

594667
private static MangledComprehensionAst of(
595-
CelAbstractSyntaxTree ast, ImmutableSet<String> mangledComprehensionIdents) {
668+
CelAbstractSyntaxTree ast, ImmutableMap<String, CelType> mangledComprehensionIdents) {
596669
return new AutoValue_MutableAst_MangledComprehensionAst(ast, mangledComprehensionIdents);
597670
}
598671
}

optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,6 @@ private CelAbstractSyntaxTree optimizeUsingCelBlock(
130130
navigableAst.getAst(), MANGLED_COMPREHENSION_IDENTIFIER_PREFIX);
131131
CelAbstractSyntaxTree astToModify = mangledComprehensionAst.ast();
132132
CelSource sourceToModify = astToModify.getSource();
133-
ImmutableSet<CelVarDecl> mangledIdentDecls =
134-
newMangledIdentDecls(celBuilder, mangledComprehensionAst);
135133

136134
int blockIdentifierIndex = 0;
137135
int iterCount;
@@ -192,7 +190,11 @@ private CelAbstractSyntaxTree optimizeUsingCelBlock(
192190

193191
// Add all mangled comprehension identifiers to the environment, so that the subexpressions can
194192
// retain context to them.
195-
celBuilder.addVarDeclarations(mangledIdentDecls);
193+
mangledComprehensionAst
194+
.mangledComprehensionIdents()
195+
.forEach(
196+
(identName, type) ->
197+
celBuilder.addVarDeclarations(CelVarDecl.newVarDeclaration(identName, type)));
196198
// Type-check all sub-expressions then add them as block identifiers to the CEL environment
197199
addBlockIdentsToEnv(celBuilder, subexpressions);
198200

@@ -260,41 +262,6 @@ private static void addBlockIdentsToEnv(CelBuilder celBuilder, List<CelExpr> sub
260262
}
261263
}
262264

263-
private static ImmutableSet<CelVarDecl> newMangledIdentDecls(
264-
CelBuilder celBuilder, MangledComprehensionAst mangledComprehensionAst) {
265-
if (mangledComprehensionAst.mangledComprehensionIdents().isEmpty()) {
266-
return ImmutableSet.of();
267-
}
268-
CelAbstractSyntaxTree ast = mangledComprehensionAst.ast();
269-
try {
270-
ast = celBuilder.build().check(ast).getAst();
271-
} catch (CelValidationException e) {
272-
throw new IllegalStateException("Failed to type-check mangled AST.", e);
273-
}
274-
275-
ImmutableSet.Builder<CelVarDecl> mangledVarDecls = ImmutableSet.builder();
276-
for (String ident : mangledComprehensionAst.mangledComprehensionIdents()) {
277-
CelExpr mangledIdentExpr =
278-
CelNavigableAst.fromAst(ast)
279-
.getRoot()
280-
.allNodes()
281-
.filter(node -> node.getKind().equals(Kind.IDENT))
282-
.map(CelNavigableExpr::expr)
283-
.filter(expr -> expr.ident().name().equals(ident))
284-
.findAny()
285-
.orElse(null);
286-
if (mangledIdentExpr == null) {
287-
break;
288-
}
289-
290-
CelType mangledIdentType =
291-
ast.getType(mangledIdentExpr.id()).orElseThrow(() -> new NoSuchElementException("?"));
292-
mangledVarDecls.add(CelVarDecl.newVarDeclaration(ident, mangledIdentType));
293-
}
294-
295-
return mangledVarDecls.build();
296-
}
297-
298265
private CelAbstractSyntaxTree optimizeUsingCelBind(CelNavigableAst navigableAst) {
299266
CelAbstractSyntaxTree astToModify =
300267
mutableAst

optimizer/src/test/java/dev/cel/optimizer/MutableAstTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ public void mangleComprehensionVariable_singleMacro() throws Exception {
687687
assertThat(mangledAst.getExpr().toString())
688688
.isEqualTo(
689689
"COMPREHENSION [13] {\n"
690-
+ " iter_var: @c0\n"
690+
+ " iter_var: @c0:0\n"
691691
+ " iter_range: {\n"
692692
+ " CREATE_LIST [1] {\n"
693693
+ " elements: {\n"
@@ -722,7 +722,7 @@ public void mangleComprehensionVariable_singleMacro() throws Exception {
722722
+ " name: __result__\n"
723723
+ " }\n"
724724
+ " IDENT [5] {\n"
725-
+ " name: @c0\n"
725+
+ " name: @c0:0\n"
726726
+ " }\n"
727727
+ " }\n"
728728
+ " }\n"
@@ -733,7 +733,7 @@ public void mangleComprehensionVariable_singleMacro() throws Exception {
733733
+ " }\n"
734734
+ " }\n"
735735
+ "}");
736-
assertThat(CEL_UNPARSER.unparse(mangledAst)).isEqualTo("[false].exists(@c0, @c0)");
736+
assertThat(CEL_UNPARSER.unparse(mangledAst)).isEqualTo("[false].exists(@c0:0, @c0:0)");
737737
assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval()).isEqualTo(false);
738738
assertConsistentMacroCalls(ast);
739739
}
@@ -748,7 +748,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw
748748
assertThat(mangledAst.getExpr().toString())
749749
.isEqualTo(
750750
"COMPREHENSION [27] {\n"
751-
+ " iter_var: @c0\n"
751+
+ " iter_var: @c0:0\n"
752752
+ " iter_range: {\n"
753753
+ " CREATE_LIST [1] {\n"
754754
+ " elements: {\n"
@@ -785,12 +785,12 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw
785785
+ " name: __result__\n"
786786
+ " }\n"
787787
+ " COMPREHENSION [19] {\n"
788-
+ " iter_var: @c1\n"
788+
+ " iter_var: @c1:0\n"
789789
+ " iter_range: {\n"
790790
+ " CREATE_LIST [5] {\n"
791791
+ " elements: {\n"
792792
+ " IDENT [6] {\n"
793-
+ " name: @c0\n"
793+
+ " name: @c0:0\n"
794794
+ " }\n"
795795
+ " }\n"
796796
+ " }\n"
@@ -825,7 +825,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw
825825
+ " function: _==_\n"
826826
+ " args: {\n"
827827
+ " IDENT [9] {\n"
828-
+ " name: @c1\n"
828+
+ " name: @c1:0\n"
829829
+ " }\n"
830830
+ " CONSTANT [11] { value: 1 }\n"
831831
+ " }\n"
@@ -850,7 +850,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw
850850
+ "}");
851851

852852
assertThat(CEL_UNPARSER.unparse(mangledAst))
853-
.isEqualTo("[x].exists(@c0, [@c0].exists(@c1, @c1 == 1))");
853+
.isEqualTo("[x].exists(@c0:0, [@c0:0].exists(@c1:0, @c1:0 == 1))");
854854
assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval(ImmutableMap.of("x", 1)))
855855
.isEqualTo(true);
856856
assertConsistentMacroCalls(ast);

optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ java_library(
77
testonly = 1,
88
srcs = glob(["*.java"]),
99
deps = [
10-
"//:java_truth",
10+
# "//java/com/google/testing/testsize:annotations",
1111
"//bundle:cel",
1212
"//common",
1313
"//common:compiler_common",
@@ -28,16 +28,18 @@ java_library(
2828
"//parser:operator",
2929
"//parser:unparser",
3030
"//runtime",
31-
"@maven//:com_google_guava_guava",
32-
"@maven//:com_google_testparameterinjector_test_parameter_injector",
3331
"@maven//:junit_junit",
32+
"@maven//:com_google_testparameterinjector_test_parameter_injector",
33+
"//:java_truth",
34+
"@maven//:com_google_guava_guava",
3435
],
3536
)
3637

3738
junit4_test_suites(
3839
name = "test_suites",
3940
sizes = [
4041
"small",
42+
"medium",
4143
],
4244
src_dir = "src/test/java",
4345
deps = [":tests"],

0 commit comments

Comments
 (0)