|
41 | 41 | import dev.cel.common.CelSource.Extension; |
42 | 42 | import dev.cel.common.CelSource.Extension.Component; |
43 | 43 | import dev.cel.common.CelSource.Extension.Version; |
| 44 | +import dev.cel.common.Operator; |
44 | 45 | import dev.cel.common.ast.CelConstant; |
45 | 46 | import dev.cel.common.ast.CelExpr.ExprKind.Kind; |
46 | 47 | import dev.cel.common.ast.CelExprIdGeneratorFactory; |
|
59 | 60 | import dev.cel.common.navigation.CelNavigableMutableExpr; |
60 | 61 | import dev.cel.common.navigation.TraversalOrder; |
61 | 62 | import dev.cel.common.types.CelKind; |
| 63 | +import dev.cel.common.types.CelType; |
62 | 64 | import dev.cel.common.types.CelTypes; |
63 | 65 | import dev.cel.common.types.ListType; |
| 66 | +import dev.cel.common.types.OptionalType; |
64 | 67 | import dev.cel.common.types.SimpleType; |
65 | 68 | import dev.cel.common.types.TypeParamType; |
66 | 69 | import dev.cel.common.values.CelByteString; |
|
122 | 125 | * |
123 | 126 | * <p>Map indexing and non-protobuf selects pass through untouched. |
124 | 127 | * |
125 | | - * <p><b>Rename Resilience & Dynamic Type Limitations:</b> |
| 128 | + * <p><b>Limitations:</b> |
126 | 129 | * |
127 | 130 | * <ul> |
128 | 131 | * <li><b>Protobuf Extensions:</b> Extension fields are not rename-resilient; runtime lookup |
|
131 | 134 | * from {@code google.protobuf.Any}, or evaluated via classless wire payloads ({@code |
132 | 135 | * RawProtoMessageLiteValue}), no cross-check between the embedded field number and field name |
133 | 136 | * is performed at runtime. |
| 137 | + * <li><b>Oneofs:</b> The lite runtime reads fields from the wire, so it reports every serialized |
| 138 | + * member of a oneof as present. Protobuf serializes at most one, unless an older-schema |
| 139 | + * binary merges or mutates the message, or the bytes are concatenated or altered by hand. |
| 140 | + * <li><b>Optionals:</b> Optional field selection on a message ({@code msg.?field}, or a field of |
| 141 | + * an optional message) is not yet supported and throws during optimization. |
134 | 142 | * </ul> |
135 | 143 | */ |
136 | 144 | public final class SelectOptimizer implements CelAstOptimizer { |
@@ -213,6 +221,7 @@ public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) { |
213 | 221 |
|
214 | 222 | CelMutableAst astToModify = CelMutableAst.fromCelAst(ast); |
215 | 223 | CelNavigableMutableAst navAst = CelNavigableMutableAst.fromAst(astToModify); |
| 224 | + navAst.getRoot().allNodes().forEach(node -> checkNotOptionalFieldSelection(navAst, node)); |
216 | 225 | ImmutableList<CelNavigableMutableExpr> topOfChainSelects = |
217 | 226 | navAst |
218 | 227 | .getRoot() |
@@ -259,7 +268,6 @@ private void rewriteSelectChain( |
259 | 268 | () -> new IllegalStateException("Expected optimizable field on select node")); |
260 | 269 | fields.add(topField); |
261 | 270 |
|
262 | | - // TODO: Support optional field selection (_?._) once integrated with lite runtime. |
263 | 271 | CelMutableExpr currentExpr = topNode.expr().select().operand(); |
264 | 272 | while (currentExpr.getKind() == Kind.SELECT) { |
265 | 273 | CelMutableSelect select = currentExpr.select(); |
@@ -357,11 +365,43 @@ private Optional<FieldDescriptor> getOptimizableField( |
357 | 365 |
|
358 | 366 | private Optional<FieldDescriptor> getOptimizableFieldForExpr( |
359 | 367 | CelNavigableMutableAst navAst, CelMutableSelect select) { |
360 | | - return navAst |
361 | | - .getType(select.operand().id()) |
362 | | - .filter(type -> type.kind() == CelKind.STRUCT) |
363 | | - .flatMap(type -> descriptorPool.findDescriptor(type.name())) |
364 | | - .map(desc -> desc.findFieldByName(select.field())); |
| 368 | + return navAst.getType(select.operand().id()).flatMap(type -> findField(type, select.field())); |
| 369 | + } |
| 370 | + |
| 371 | + private Optional<FieldDescriptor> findField(CelType type, String field) { |
| 372 | + if (type.kind() != CelKind.STRUCT) { |
| 373 | + return Optional.empty(); |
| 374 | + } |
| 375 | + return descriptorPool.findDescriptor(type.name()).map(desc -> desc.findFieldByName(field)); |
| 376 | + } |
| 377 | + |
| 378 | + // TODO: Support optional field selection once integrated with lite runtime. |
| 379 | + private void checkNotOptionalFieldSelection( |
| 380 | + CelNavigableMutableAst navAst, CelNavigableMutableExpr node) { |
| 381 | + Optional<FieldDescriptor> field = Optional.empty(); |
| 382 | + if (node.getKind() == Kind.SELECT) { |
| 383 | + // A field of an optional message, e.g. `.bb` in `msg.repeated_nested_message[?0].bb` |
| 384 | + CelMutableSelect select = node.expr().select(); |
| 385 | + field = |
| 386 | + navAst |
| 387 | + .getType(select.operand().id()) |
| 388 | + .filter(OptionalType.class::isInstance) |
| 389 | + .flatMap(type -> findField(type.parameters().get(0), select.field())); |
| 390 | + } else if (node.getKind() == Kind.CALL |
| 391 | + && node.expr().call().function().equals(Operator.OPTIONAL_SELECT.getFunction())) { |
| 392 | + // `msg.?field`, where `msg` may itself be optional |
| 393 | + List<CelMutableExpr> args = node.expr().call().args(); |
| 394 | + field = |
| 395 | + navAst |
| 396 | + .getType(args.get(0).id()) |
| 397 | + .map(type -> type instanceof OptionalType ? type.parameters().get(0) : type) |
| 398 | + .flatMap(type -> findField(type, args.get(1).constant().stringValue())); |
| 399 | + } |
| 400 | + if (field.isPresent()) { |
| 401 | + throw new UnsupportedOperationException( |
| 402 | + "Optimization of optional field selection is currently unimplemented: " |
| 403 | + + field.get().getFullName()); |
| 404 | + } |
365 | 405 | } |
366 | 406 |
|
367 | 407 | private static CelMutableExpr resolveDummyOrDefExpr( |
|
0 commit comments