|
21 | 21 | import com.google.auto.value.AutoValue; |
22 | 22 | import com.google.common.annotations.VisibleForTesting; |
23 | 23 | import com.google.common.base.Preconditions; |
| 24 | +import com.google.common.collect.HashBasedTable; |
24 | 25 | import com.google.common.collect.ImmutableList; |
25 | 26 | import com.google.common.collect.ImmutableMap; |
26 | | -import com.google.common.collect.ImmutableSet; |
| 27 | +import com.google.common.collect.Table; |
27 | 28 | import com.google.errorprone.annotations.Immutable; |
28 | 29 | import dev.cel.common.CelAbstractSyntaxTree; |
29 | 30 | import dev.cel.common.CelSource; |
|
38 | 39 | import dev.cel.common.navigation.CelNavigableAst; |
39 | 40 | import dev.cel.common.navigation.CelNavigableExpr; |
40 | 41 | import dev.cel.common.navigation.CelNavigableExpr.TraversalOrder; |
| 42 | +import dev.cel.common.types.CelType; |
| 43 | +import java.util.HashMap; |
| 44 | +import java.util.LinkedHashMap; |
41 | 45 | import java.util.Map.Entry; |
42 | 46 | import java.util.NoSuchElementException; |
43 | 47 | import java.util.Optional; |
| 48 | +import java.util.stream.Collectors; |
44 | 49 |
|
45 | 50 | /** MutableAst contains logic for mutating a {@link CelAbstractSyntaxTree}. */ |
46 | 51 | @Immutable |
@@ -187,45 +192,112 @@ public CelAbstractSyntaxTree renumberIdsConsecutively(CelAbstractSyntaxTree ast) |
187 | 192 | * |
188 | 193 | * <p>The expression IDs are not modified when the identifier names are changed. |
189 | 194 | * |
| 195 | + * <p>Mangling occurs only if the iteration variable is referenced within the loop step. |
| 196 | + * |
190 | 197 | * <p>Iteration variables in comprehensions are numbered based on their comprehension nesting |
191 | | - * levels. Examples: |
| 198 | + * levels and the iteration variable's type. Examples: |
192 | 199 | * |
193 | 200 | * <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))} |
198 | 208 | * </ul> |
199 | 209 | * |
200 | 210 | * @param ast AST to mutate |
201 | 211 | * @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. |
203 | 213 | */ |
204 | 214 | public MangledComprehensionAst mangleComprehensionIdentifierNames( |
205 | 215 | CelAbstractSyntaxTree ast, String newIdentPrefix) { |
206 | | - int iterCount; |
207 | 216 | 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. |
210 | 274 | CelNavigableExpr comprehensionNode = |
211 | 275 | newNavigableAst |
212 | 276 | .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. |
215 | 277 | .allNodes(TraversalOrder.POST_ORDER) |
216 | 278 | .filter(node -> node.getKind().equals(Kind.COMPREHENSION)) |
217 | 279 | .filter(node -> !node.expr().comprehension().iterVar().startsWith(newIdentPrefix)) |
218 | 280 | .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(); |
223 | 284 |
|
224 | 285 | CelExpr.Builder comprehensionExpr = comprehensionNode.expr().toBuilder(); |
225 | 286 | String iterVar = comprehensionExpr.comprehension().iterVar(); |
226 | 287 | 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); |
229 | 301 |
|
230 | 302 | CelExpr.Builder mutatedComprehensionExpr = |
231 | 303 | mangleIdentsInComprehensionExpr( |
@@ -254,7 +326,8 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( |
254 | 326 | throw new IllegalStateException("Max iteration count reached."); |
255 | 327 | } |
256 | 328 |
|
257 | | - return MangledComprehensionAst.of(newNavigableAst.getAst(), mangledComprehensionIdents.build()); |
| 329 | + return MangledComprehensionAst.of( |
| 330 | + newNavigableAst.getAst(), ImmutableMap.copyOf(mangledIdentNamesToType)); |
258 | 331 | } |
259 | 332 |
|
260 | 333 | /** |
@@ -588,11 +661,11 @@ public abstract static class MangledComprehensionAst { |
588 | 661 | /** AST after the iteration variables have been mangled. */ |
589 | 662 | public abstract CelAbstractSyntaxTree ast(); |
590 | 663 |
|
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(); |
593 | 666 |
|
594 | 667 | private static MangledComprehensionAst of( |
595 | | - CelAbstractSyntaxTree ast, ImmutableSet<String> mangledComprehensionIdents) { |
| 668 | + CelAbstractSyntaxTree ast, ImmutableMap<String, CelType> mangledComprehensionIdents) { |
596 | 669 | return new AutoValue_MutableAst_MangledComprehensionAst(ast, mangledComprehensionIdents); |
597 | 670 | } |
598 | 671 | } |
|
0 commit comments