Skip to content

Commit cc35883

Browse files
l46kokcopybara-github
authored andcommitted
Fail fast on optional field selection in SelectOptimizer
PiperOrigin-RevId: 998708843
1 parent fb716e5 commit cc35883

3 files changed

Lines changed: 81 additions & 23 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ java_library(
128128
"//common:cel_source",
129129
"//common:compiler_common",
130130
"//common:mutable_ast",
131+
"//common:operator",
131132
"//common/ast",
132133
"//common/ast:expr_factory",
133134
"//common/ast:mutable_expr",

‎optimizer/src/main/java/dev/cel/optimizer/optimizers/SelectOptimizer.java‎

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import dev.cel.common.CelSource.Extension;
4242
import dev.cel.common.CelSource.Extension.Component;
4343
import dev.cel.common.CelSource.Extension.Version;
44+
import dev.cel.common.Operator;
4445
import dev.cel.common.ast.CelConstant;
4546
import dev.cel.common.ast.CelExpr.ExprKind.Kind;
4647
import dev.cel.common.ast.CelExprIdGeneratorFactory;
@@ -59,8 +60,10 @@
5960
import dev.cel.common.navigation.CelNavigableMutableExpr;
6061
import dev.cel.common.navigation.TraversalOrder;
6162
import dev.cel.common.types.CelKind;
63+
import dev.cel.common.types.CelType;
6264
import dev.cel.common.types.CelTypes;
6365
import dev.cel.common.types.ListType;
66+
import dev.cel.common.types.OptionalType;
6467
import dev.cel.common.types.SimpleType;
6568
import dev.cel.common.types.TypeParamType;
6669
import dev.cel.common.values.CelByteString;
@@ -122,7 +125,7 @@
122125
*
123126
* <p>Map indexing and non-protobuf selects pass through untouched.
124127
*
125-
* <p><b>Rename Resilience & Dynamic Type Limitations:</b>
128+
* <p><b>Limitations:</b>
126129
*
127130
* <ul>
128131
* <li><b>Protobuf Extensions:</b> Extension fields are not rename-resilient; runtime lookup
@@ -131,6 +134,11 @@
131134
* from {@code google.protobuf.Any}, or evaluated via classless wire payloads ({@code
132135
* RawProtoMessageLiteValue}), no cross-check between the embedded field number and field name
133136
* 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.
134142
* </ul>
135143
*/
136144
public final class SelectOptimizer implements CelAstOptimizer {
@@ -213,6 +221,7 @@ public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) {
213221

214222
CelMutableAst astToModify = CelMutableAst.fromCelAst(ast);
215223
CelNavigableMutableAst navAst = CelNavigableMutableAst.fromAst(astToModify);
224+
navAst.getRoot().allNodes().forEach(node -> checkNotOptionalFieldSelection(navAst, node));
216225
ImmutableList<CelNavigableMutableExpr> topOfChainSelects =
217226
navAst
218227
.getRoot()
@@ -259,7 +268,6 @@ private void rewriteSelectChain(
259268
() -> new IllegalStateException("Expected optimizable field on select node"));
260269
fields.add(topField);
261270

262-
// TODO: Support optional field selection (_?._) once integrated with lite runtime.
263271
CelMutableExpr currentExpr = topNode.expr().select().operand();
264272
while (currentExpr.getKind() == Kind.SELECT) {
265273
CelMutableSelect select = currentExpr.select();
@@ -357,11 +365,43 @@ private Optional<FieldDescriptor> getOptimizableField(
357365

358366
private Optional<FieldDescriptor> getOptimizableFieldForExpr(
359367
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+
}
365405
}
366406

367407
private static CelMutableExpr resolveDummyOrDefExpr(

‎optimizer/src/test/java/dev/cel/optimizer/optimizers/SelectOptimizerTest.java‎

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ private enum RewriteTestCase {
367367
+ "cel.@attribute(msg, [[227, \"map_string_message\", -1, [9, 11]]],"
368368
+ " {\"\": cel.expr.conformance.proto3.TestAllTypes.NestedMessage{}}).key, "
369369
+ "[[1, \"bb\"]])"),
370+
PROTO_MAP_FIELD_SELECT_STOPS_AT_ANY_BOUNDARY(
371+
"msg.map_string_any['k'].value",
372+
"cel.@attribute(msg, [[311, \"map_string_any\", -1, [9, 11]]],"
373+
+ " {\"\": google.protobuf.Any{}})[\"k\"].value"),
370374

371375
MIXED_BOOLEAN_EXPRESSION(
372376
"msg.single_int64 > 0 && has(msg.single_nested_message)",
@@ -1469,24 +1473,37 @@ public void optimize_comprehensionOverRepeatedMessageField_preservesStaticElemen
14691473
}
14701474

14711475
@Test
1472-
public void optimize_optionalSelect_passesThroughUntouched() throws Exception {
1473-
Cel celWithOptional =
1474-
cel.toCelBuilder()
1475-
.addCompilerLibraries(CelExtensions.optional())
1476-
.addRuntimeLibraries(CelExtensions.optional())
1477-
.build();
1478-
CelOptimizer optimizer =
1479-
CelOptimizerFactory.standardCelOptimizerBuilder(celWithOptional)
1480-
.addAstOptimizers(
1481-
SelectOptimizer.newInstance(
1482-
SelectOptimizerOptions.newBuilder().build(),
1483-
TestAllTypes.getDescriptor().getFile()))
1484-
.build();
1485-
CelAbstractSyntaxTree ast = celWithOptional.compile("msg.?single_nested_message.bb").getAst();
1476+
public void optimize_optionalFieldSelection_throwsUnsupportedOperationException(
1477+
@TestParameter({
1478+
"msg.?single_nested_message",
1479+
"msg.repeated_nested_message[?0].?bb",
1480+
"msg.repeated_nested_message[?0].bb"
1481+
})
1482+
String expression)
1483+
throws Exception {
1484+
Cel celWithOptional = cel.toCelBuilder().addCompilerLibraries(CelExtensions.optional()).build();
1485+
CelAbstractSyntaxTree ast = celWithOptional.compile(expression).getAst();
1486+
SelectOptimizer optimizer = SelectOptimizer.newInstance(TestAllTypes.getDescriptor().getFile());
14861487

1487-
CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast);
1488+
UnsupportedOperationException e =
1489+
assertThrows(
1490+
UnsupportedOperationException.class, () -> optimizer.optimize(ast, celWithOptional));
1491+
1492+
assertThat(e)
1493+
.hasMessageThat()
1494+
.contains("Optimization of optional field selection is currently unimplemented");
1495+
}
14881496

1489-
assertThat(optimizedAst.getExpr()).isEqualTo(ast.getExpr());
1497+
@Test
1498+
public void optimize_optionalMapKeySelection_rewritesMapField() throws Exception {
1499+
Cel celWithOptional = cel.toCelBuilder().addCompilerLibraries(CelExtensions.optional()).build();
1500+
CelAbstractSyntaxTree ast = celWithOptional.compile("msg.map_string_string.?key").getAst();
1501+
1502+
CelAbstractSyntaxTree optimizedAst = newSelectOptimizer(celWithOptional).optimize(ast);
1503+
1504+
assertThat(CEL_UNPARSER.unparse(optimizedAst))
1505+
.isEqualTo(
1506+
"cel.@attribute(msg, [[61, \"map_string_string\", -1, [9, 9]]], {\"\": \"\"}).?key");
14901507
}
14911508

14921509
private static CelOptimizer newSelectOptimizer(Cel cel) {

0 commit comments

Comments
 (0)