Skip to content

Commit d917045

Browse files
l46kokcopybara-github
authored andcommitted
Ensure chained optional field selection does not repeatedly wrap the optional type
Fixes #1083 PiperOrigin-RevId: 931223677
1 parent f31ad58 commit d917045

2 files changed

Lines changed: 41 additions & 2 deletions

File tree

extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import dev.cel.common.values.CelByteString;
4545
import dev.cel.common.values.NullValue;
4646
import dev.cel.compiler.CelCompiler;
47+
import dev.cel.expr.conformance.proto3.NestedTestAllTypes;
4748
import dev.cel.expr.conformance.proto3.TestAllTypes;
4849
import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage;
4950
import dev.cel.parser.CelMacro;
@@ -603,6 +604,16 @@ public void optionalFieldSelection_onMap_returnsOptionalValue() throws Exception
603604
assertThat(result).hasValue(2L);
604605
}
605606

607+
@Test
608+
public void optionalFieldSelection_onMap_chained_returnsSinglyWrappedOptional() throws Exception {
609+
Cel cel = newCelBuilder().setResultType(OptionalType.create(SimpleType.STRING)).build();
610+
CelAbstractSyntaxTree ast = compile(cel, "{'foo': {'bar': 'baz'}}.?foo.?bar");
611+
612+
Optional<String> result = (Optional<String>) cel.createProgram(ast).eval();
613+
614+
assertThat(result).hasValue("baz");
615+
}
616+
606617
@Test
607618
public void optionalFieldSelection_onProtoMessage_returnsOptionalEmpty() throws Exception {
608619
Cel cel =
@@ -619,6 +630,30 @@ public void optionalFieldSelection_onProtoMessage_returnsOptionalEmpty() throws
619630
assertThat(result).isEmpty();
620631
}
621632

633+
@Test
634+
public void optionalFieldSelection_onProtoMessage_chained_returnsSinglyWrappedOptional()
635+
throws Exception {
636+
Cel cel =
637+
newCelBuilder()
638+
.setResultType(OptionalType.create(SimpleType.INT))
639+
.addVar(
640+
"msg", StructTypeReference.create(NestedTestAllTypes.getDescriptor().getFullName()))
641+
.build();
642+
CelAbstractSyntaxTree ast = compile(cel, "msg.?payload.?single_int32");
643+
644+
Optional<Long> result =
645+
(Optional<Long>)
646+
cel.createProgram(ast)
647+
.eval(
648+
ImmutableMap.of(
649+
"msg",
650+
NestedTestAllTypes.newBuilder()
651+
.setPayload(TestAllTypes.newBuilder().setSingleInt32(5).build())
652+
.build()));
653+
654+
assertThat(result).hasValue(5L);
655+
}
656+
622657
@Test
623658
public void optionalFieldSelection_onProtoMessage_returnsOptionalValue() throws Exception {
624659
Cel cel =

runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -783,8 +783,12 @@ private Optional<IntermediateResult> maybeEvalOptionalSelectField(
783783
}
784784

785785
IntermediateResult result = evalFieldSelect(frame, expr, operand, field, false);
786-
return Optional.of(
787-
IntermediateResult.create(result.attribute(), Optional.of(result.value())));
786+
// Ensure only one level of optional is wrapped when chaining optional field selections.
787+
Object resultValue = result.value();
788+
if (!(resultValue instanceof Optional)) {
789+
resultValue = Optional.of(resultValue);
790+
}
791+
return Optional.of(IntermediateResult.create(result.attribute(), resultValue));
788792
}
789793

790794
private IntermediateResult evalBoolean(ExecutionFrame frame, CelExpr expr, boolean strict)

0 commit comments

Comments
 (0)