Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions common/src/main/java/dev/cel/common/internal/ProtoAdapter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,8 +204,29 @@ public Optional<Object> adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec
@SuppressWarnings({"unchecked", "rawtypes"})
public Optional<Object> adaptValueToFieldType(
FieldDescriptor fieldDescriptor, Object fieldValue) {
if (isWrapperType(fieldDescriptor) && fieldValue.equals(NullValue.NULL_VALUE)) {
return Optional.empty();
if (fieldValue instanceof NullValue) {
// `null` assignment to fields indicate that the field would not be set
// in a protobuf message (e.g: Message{msg_field: null} -> Message{})
//
// We explicitly check below for invalid null assignments, such as repeated
// or map fields. (e.g: Message{repeated_field: null} -> Error)
if (fieldDescriptor.isMapField()
|| fieldDescriptor.isRepeated()
|| fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE
|| WellKnownProto.JSON_STRUCT_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())
|| WellKnownProto.JSON_LIST_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())) {
throw new IllegalArgumentException("Unsupported field type");
}

String typeFullName = fieldDescriptor.getMessageType().getFullName();
if (!WellKnownProto.ANY_VALUE.typeName().equals(typeFullName)
&& !WellKnownProto.JSON_VALUE.typeName().equals(typeFullName)) {
return Optional.empty();
}
}
if (fieldDescriptor.isMapField()) {
Descriptor entryDescriptor = fieldDescriptor.getMessageType();
Expand DownExpand Up@@ -370,14 +391,6 @@ private static String typeName(Descriptor protoType) {
return protoType.getFullName();
}

private static boolean isWrapperType(FieldDescriptor fieldDescriptor) {
if (fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
return false;
}
String fieldTypeName = fieldDescriptor.getMessageType().getFullName();
return WellKnownProto.isWrapperType(fieldTypeName);
}

private static int intCheckedCast(long value) {
try {
return Ints.checkedCast(value);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
try {
unpackedMessage = dynamicProto.unpack((Any) message);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(
throw new IllegalArgumentException(
"Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e);
}
return toRuntimeValue(unpackedMessage);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,10 +150,7 @@ public static List<Object[]> data() {
@Test
public void adaptValueToProto_bidirectionalConversion() {
DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE);
ProtoAdapter protoAdapter =
new ProtoAdapter(
dynamicProto,
CelOptions.current().build());
ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.current().build());
assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName()))
.isEqualTo(proto);
assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value);
Expand DownExpand Up@@ -181,6 +178,18 @@ public void adaptAnyValue_hermeticTypes_bidirectionalConversion() {

@RunWith(JUnit4.class)
public static class AsymmetricConversionTest {

@Test
public void unpackAny_celNullValue() throws Exception {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Any any =
(Any)
protoAdapter.adaptValueToProto(
dev.cel.common.values.NullValue.NULL_VALUE, "google.protobuf.Any");
Object unpacked = protoAdapter.adaptProtoToValue(any);
assertThat(unpacked).isEqualTo(dev.cel.common.values.NullValue.NULL_VALUE);
}

@Test
public void adaptValueToProto_asymmetricFloatConversion() {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Expand Down
17 changes: 0 additions & 17 deletions conformance/src/test/java/dev/cel/conformance/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,14 +124,6 @@ _TESTS_TO_SKIP_LEGACY = [
"string_ext/format",
"string_ext/format_errors",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Future features for CEL 1.0
# TODO: Strong typing support for enums, specified but not implemented.
"enums/strong_proto2",
Expand DownExpand Up@@ -162,7 +154,6 @@ _TESTS_TO_SKIP_PLANNER = [
"string_ext/format_errors",

# TODO: Check behavior for go/cpp
"basic/functions/unbound",
"basic/functions/unbound_is_runtime_error",

# TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms.
Expand All@@ -177,14 +168,6 @@ _TESTS_TO_SKIP_PLANNER = [
# Skip until fixed.
"parse/receiver_function_names",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Type inference edgecases around null(able) assignability.
# These type check, but resolve to a different type.
# list(int), want list(wrapper(int))
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,10 +210,10 @@ public void evaluate() throws Throwable {
}

CelRuntime runtime = getRuntime(test, usePlanner);
Program program = runtime.createProgram(response.getAst());
ExprValue result = null;
CelEvaluationException error = null;
try {
Program program = runtime.createProgram(response.getAst());
result = toExprValue(program.eval(getBindings(test)), response.getAst().getResultType());
} catch (CelEvaluationException e) {
error = e;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,11 @@ public void dynamicMessage_dynamicDescriptor() throws Exception {

// All the tests below rely on message creation with fields populated. They are excluded for time
// being until this support is added.
@Override
public void nullAssignability() throws Exception {
skipBaselineVerification();
}

@Override
public void wrappers() throws Exception {
skipBaselineVerification();
Expand Down
35 changes: 35 additions & 0 deletions runtime/src/test/resources/nullAssignability.baseline
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
Source: TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: TestAllTypes{}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)
=====>
bindings: {}
result: false

Source: TestAllTypes{single_value: null}.single_value == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_value: null}.single_value)
=====>
bindings: {}
result: true

Source: TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_timestamp: null}.single_timestamp)
=====>
bindings: {}
result: false

25 changes: 25 additions & 0 deletions testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -2122,6 +2122,31 @@ public void wrappers() throws Exception {
runTest();
}

@Test
public void nullAssignability() throws Exception {
setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()));
source = "TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null";
runTest();

source = "TestAllTypes{}.single_int64_wrapper == null";
runTest();

source = "has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)";
runTest();

source = "TestAllTypes{single_value: null}.single_value == null";
runTest();

source = "has(TestAllTypes{single_value: null}.single_value)";
runTest();

source = "TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)";
runTest();

source = "has(TestAllTypes{single_timestamp: null}.single_timestamp)";
runTest();
}

@Test
public void longComprehension() {
ImmutableList<Long> l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList());
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions common/src/main/java/dev/cel/common/internal/ProtoAdapter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,8 +204,29 @@ public Optional<Object> adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec
@SuppressWarnings({"unchecked", "rawtypes"})
public Optional<Object> adaptValueToFieldType(
FieldDescriptor fieldDescriptor, Object fieldValue) {
if (isWrapperType(fieldDescriptor) && fieldValue.equals(NullValue.NULL_VALUE)) {
return Optional.empty();
if (fieldValue instanceof NullValue) {
// `null` assignment to fields indicate that the field would not be set
// in a protobuf message (e.g: Message{msg_field: null} -> Message{})
//
// We explicitly check below for invalid null assignments, such as repeated
// or map fields. (e.g: Message{repeated_field: null} -> Error)
if (fieldDescriptor.isMapField()
|| fieldDescriptor.isRepeated()
|| fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE
|| WellKnownProto.JSON_STRUCT_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())
|| WellKnownProto.JSON_LIST_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())) {
throw new IllegalArgumentException("Unsupported field type");
}

String typeFullName = fieldDescriptor.getMessageType().getFullName();
if (!WellKnownProto.ANY_VALUE.typeName().equals(typeFullName)
&& !WellKnownProto.JSON_VALUE.typeName().equals(typeFullName)) {
return Optional.empty();
}
}
if (fieldDescriptor.isMapField()) {
Descriptor entryDescriptor = fieldDescriptor.getMessageType();
Expand DownExpand Up@@ -370,14 +391,6 @@ private static String typeName(Descriptor protoType) {
return protoType.getFullName();
}

private static boolean isWrapperType(FieldDescriptor fieldDescriptor) {
if (fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
return false;
}
String fieldTypeName = fieldDescriptor.getMessageType().getFullName();
return WellKnownProto.isWrapperType(fieldTypeName);
}

private static int intCheckedCast(long value) {
try {
return Ints.checkedCast(value);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
try {
unpackedMessage = dynamicProto.unpack((Any) message);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(
throw new IllegalArgumentException(
"Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e);
}
return toRuntimeValue(unpackedMessage);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,10 +150,7 @@ public static List<Object[]> data() {
@Test
public void adaptValueToProto_bidirectionalConversion() {
DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE);
ProtoAdapter protoAdapter =
new ProtoAdapter(
dynamicProto,
CelOptions.current().build());
ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.current().build());
assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName()))
.isEqualTo(proto);
assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value);
Expand DownExpand Up@@ -181,6 +178,18 @@ public void adaptAnyValue_hermeticTypes_bidirectionalConversion() {

@RunWith(JUnit4.class)
public static class AsymmetricConversionTest {

@Test
public void unpackAny_celNullValue() throws Exception {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Any any =
(Any)
protoAdapter.adaptValueToProto(
dev.cel.common.values.NullValue.NULL_VALUE, "google.protobuf.Any");
Object unpacked = protoAdapter.adaptProtoToValue(any);
assertThat(unpacked).isEqualTo(dev.cel.common.values.NullValue.NULL_VALUE);
}

@Test
public void adaptValueToProto_asymmetricFloatConversion() {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Expand Down
17 changes: 0 additions & 17 deletions conformance/src/test/java/dev/cel/conformance/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,14 +124,6 @@ _TESTS_TO_SKIP_LEGACY = [
"string_ext/format",
"string_ext/format_errors",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Future features for CEL 1.0
# TODO: Strong typing support for enums, specified but not implemented.
"enums/strong_proto2",
Expand DownExpand Up@@ -162,7 +154,6 @@ _TESTS_TO_SKIP_PLANNER = [
"string_ext/format_errors",

# TODO: Check behavior for go/cpp
"basic/functions/unbound",
"basic/functions/unbound_is_runtime_error",

# TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms.
Expand All@@ -177,14 +168,6 @@ _TESTS_TO_SKIP_PLANNER = [
# Skip until fixed.
"parse/receiver_function_names",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Type inference edgecases around null(able) assignability.
# These type check, but resolve to a different type.
# list(int), want list(wrapper(int))
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,10 +210,10 @@ public void evaluate() throws Throwable {
}

CelRuntime runtime = getRuntime(test, usePlanner);
Program program = runtime.createProgram(response.getAst());
ExprValue result = null;
CelEvaluationException error = null;
try {
Program program = runtime.createProgram(response.getAst());
result = toExprValue(program.eval(getBindings(test)), response.getAst().getResultType());
} catch (CelEvaluationException e) {
error = e;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,11 @@ public void dynamicMessage_dynamicDescriptor() throws Exception {

// All the tests below rely on message creation with fields populated. They are excluded for time
// being until this support is added.
@Override
public void nullAssignability() throws Exception {
skipBaselineVerification();
}

@Override
public void wrappers() throws Exception {
skipBaselineVerification();
Expand Down
35 changes: 35 additions & 0 deletions runtime/src/test/resources/nullAssignability.baseline
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
Source: TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: TestAllTypes{}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)
=====>
bindings: {}
result: false

Source: TestAllTypes{single_value: null}.single_value == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_value: null}.single_value)
=====>
bindings: {}
result: true

Source: TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_timestamp: null}.single_timestamp)
=====>
bindings: {}
result: false

25 changes: 25 additions & 0 deletions testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -2122,6 +2122,31 @@ public void wrappers() throws Exception {
runTest();
}

@Test
public void nullAssignability() throws Exception {
setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()));
source = "TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null";
runTest();

source = "TestAllTypes{}.single_int64_wrapper == null";
runTest();

source = "has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)";
runTest();

source = "TestAllTypes{single_value: null}.single_value == null";
runTest();

source = "has(TestAllTypes{single_value: null}.single_value)";
runTest();

source = "TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)";
runTest();

source = "has(TestAllTypes{single_timestamp: null}.single_timestamp)";
runTest();
}

@Test
public void longComprehension() {
ImmutableList<Long> l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList());
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions common/src/main/java/dev/cel/common/internal/ProtoAdapter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,8 +204,29 @@ public Optional<Object> adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec
@SuppressWarnings({"unchecked", "rawtypes"})
public Optional<Object> adaptValueToFieldType(
FieldDescriptor fieldDescriptor, Object fieldValue) {
if (isWrapperType(fieldDescriptor) && fieldValue.equals(NullValue.NULL_VALUE)) {
return Optional.empty();
if (fieldValue instanceof NullValue) {
// `null` assignment to fields indicate that the field would not be set
// in a protobuf message (e.g: Message{msg_field: null} -> Message{})
//
// We explicitly check below for invalid null assignments, such as repeated
// or map fields. (e.g: Message{repeated_field: null} -> Error)
if (fieldDescriptor.isMapField()
|| fieldDescriptor.isRepeated()
|| fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE
|| WellKnownProto.JSON_STRUCT_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())
|| WellKnownProto.JSON_LIST_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())) {
throw new IllegalArgumentException("Unsupported field type");
}

String typeFullName = fieldDescriptor.getMessageType().getFullName();
if (!WellKnownProto.ANY_VALUE.typeName().equals(typeFullName)
&& !WellKnownProto.JSON_VALUE.typeName().equals(typeFullName)) {
return Optional.empty();
}
}
if (fieldDescriptor.isMapField()) {
Descriptor entryDescriptor = fieldDescriptor.getMessageType();
Expand DownExpand Up@@ -370,14 +391,6 @@ private static String typeName(Descriptor protoType) {
return protoType.getFullName();
}

private static boolean isWrapperType(FieldDescriptor fieldDescriptor) {
if (fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
return false;
}
String fieldTypeName = fieldDescriptor.getMessageType().getFullName();
return WellKnownProto.isWrapperType(fieldTypeName);
}

private static int intCheckedCast(long value) {
try {
return Ints.checkedCast(value);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
try {
unpackedMessage = dynamicProto.unpack((Any) message);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(
throw new IllegalArgumentException(
"Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e);
}
return toRuntimeValue(unpackedMessage);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,10 +150,7 @@ public static List<Object[]> data() {
@Test
public void adaptValueToProto_bidirectionalConversion() {
DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE);
ProtoAdapter protoAdapter =
new ProtoAdapter(
dynamicProto,
CelOptions.current().build());
ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.current().build());
assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName()))
.isEqualTo(proto);
assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value);
Expand DownExpand Up@@ -181,6 +178,18 @@ public void adaptAnyValue_hermeticTypes_bidirectionalConversion() {

@RunWith(JUnit4.class)
public static class AsymmetricConversionTest {

@Test
public void unpackAny_celNullValue() throws Exception {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Any any =
(Any)
protoAdapter.adaptValueToProto(
dev.cel.common.values.NullValue.NULL_VALUE, "google.protobuf.Any");
Object unpacked = protoAdapter.adaptProtoToValue(any);
assertThat(unpacked).isEqualTo(dev.cel.common.values.NullValue.NULL_VALUE);
}

@Test
public void adaptValueToProto_asymmetricFloatConversion() {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Expand Down
17 changes: 0 additions & 17 deletions conformance/src/test/java/dev/cel/conformance/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,14 +124,6 @@ _TESTS_TO_SKIP_LEGACY = [
"string_ext/format",
"string_ext/format_errors",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Future features for CEL 1.0
# TODO: Strong typing support for enums, specified but not implemented.
"enums/strong_proto2",
Expand DownExpand Up@@ -162,7 +154,6 @@ _TESTS_TO_SKIP_PLANNER = [
"string_ext/format_errors",

# TODO: Check behavior for go/cpp
"basic/functions/unbound",
"basic/functions/unbound_is_runtime_error",

# TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms.
Expand All@@ -177,14 +168,6 @@ _TESTS_TO_SKIP_PLANNER = [
# Skip until fixed.
"parse/receiver_function_names",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Type inference edgecases around null(able) assignability.
# These type check, but resolve to a different type.
# list(int), want list(wrapper(int))
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,10 +210,10 @@ public void evaluate() throws Throwable {
}

CelRuntime runtime = getRuntime(test, usePlanner);
Program program = runtime.createProgram(response.getAst());
ExprValue result = null;
CelEvaluationException error = null;
try {
Program program = runtime.createProgram(response.getAst());
result = toExprValue(program.eval(getBindings(test)), response.getAst().getResultType());
} catch (CelEvaluationException e) {
error = e;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,11 @@ public void dynamicMessage_dynamicDescriptor() throws Exception {

// All the tests below rely on message creation with fields populated. They are excluded for time
// being until this support is added.
@Override
public void nullAssignability() throws Exception {
skipBaselineVerification();
}

@Override
public void wrappers() throws Exception {
skipBaselineVerification();
Expand Down
35 changes: 35 additions & 0 deletions runtime/src/test/resources/nullAssignability.baseline
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
Source: TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: TestAllTypes{}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)
=====>
bindings: {}
result: false

Source: TestAllTypes{single_value: null}.single_value == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_value: null}.single_value)
=====>
bindings: {}
result: true

Source: TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_timestamp: null}.single_timestamp)
=====>
bindings: {}
result: false

25 changes: 25 additions & 0 deletions testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -2122,6 +2122,31 @@ public void wrappers() throws Exception {
runTest();
}

@Test
public void nullAssignability() throws Exception {
setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()));
source = "TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null";
runTest();

source = "TestAllTypes{}.single_int64_wrapper == null";
runTest();

source = "has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)";
runTest();

source = "TestAllTypes{single_value: null}.single_value == null";
runTest();

source = "has(TestAllTypes{single_value: null}.single_value)";
runTest();

source = "TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)";
runTest();

source = "has(TestAllTypes{single_timestamp: null}.single_timestamp)";
runTest();
}

@Test
public void longComprehension() {
ImmutableList<Long> l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList());
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions common/src/main/java/dev/cel/common/internal/ProtoAdapter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,8 +204,29 @@ public Optional<Object> adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec
@SuppressWarnings({"unchecked", "rawtypes"})
public Optional<Object> adaptValueToFieldType(
FieldDescriptor fieldDescriptor, Object fieldValue) {
if (isWrapperType(fieldDescriptor) && fieldValue.equals(NullValue.NULL_VALUE)) {
return Optional.empty();
if (fieldValue instanceof NullValue) {
// `null` assignment to fields indicate that the field would not be set
// in a protobuf message (e.g: Message{msg_field: null} -> Message{})
//
// We explicitly check below for invalid null assignments, such as repeated
// or map fields. (e.g: Message{repeated_field: null} -> Error)
if (fieldDescriptor.isMapField()
|| fieldDescriptor.isRepeated()
|| fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE
|| WellKnownProto.JSON_STRUCT_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())
|| WellKnownProto.JSON_LIST_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())) {
throw new IllegalArgumentException("Unsupported field type");
}

String typeFullName = fieldDescriptor.getMessageType().getFullName();
if (!WellKnownProto.ANY_VALUE.typeName().equals(typeFullName)
&& !WellKnownProto.JSON_VALUE.typeName().equals(typeFullName)) {
return Optional.empty();
}
}
if (fieldDescriptor.isMapField()) {
Descriptor entryDescriptor = fieldDescriptor.getMessageType();
Expand DownExpand Up@@ -370,14 +391,6 @@ private static String typeName(Descriptor protoType) {
return protoType.getFullName();
}

private static boolean isWrapperType(FieldDescriptor fieldDescriptor) {
if (fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
return false;
}
String fieldTypeName = fieldDescriptor.getMessageType().getFullName();
return WellKnownProto.isWrapperType(fieldTypeName);
}

private static int intCheckedCast(long value) {
try {
return Ints.checkedCast(value);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
try {
unpackedMessage = dynamicProto.unpack((Any) message);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(
throw new IllegalArgumentException(
"Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e);
}
return toRuntimeValue(unpackedMessage);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,10 +150,7 @@ public static List<Object[]> data() {
@Test
public void adaptValueToProto_bidirectionalConversion() {
DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE);
ProtoAdapter protoAdapter =
new ProtoAdapter(
dynamicProto,
CelOptions.current().build());
ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.current().build());
assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName()))
.isEqualTo(proto);
assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value);
Expand DownExpand Up@@ -181,6 +178,18 @@ public void adaptAnyValue_hermeticTypes_bidirectionalConversion() {

@RunWith(JUnit4.class)
public static class AsymmetricConversionTest {

@Test
public void unpackAny_celNullValue() throws Exception {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Any any =
(Any)
protoAdapter.adaptValueToProto(
dev.cel.common.values.NullValue.NULL_VALUE, "google.protobuf.Any");
Object unpacked = protoAdapter.adaptProtoToValue(any);
assertThat(unpacked).isEqualTo(dev.cel.common.values.NullValue.NULL_VALUE);
}

@Test
public void adaptValueToProto_asymmetricFloatConversion() {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Expand Down
17 changes: 0 additions & 17 deletions conformance/src/test/java/dev/cel/conformance/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,14 +124,6 @@ _TESTS_TO_SKIP_LEGACY = [
"string_ext/format",
"string_ext/format_errors",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Future features for CEL 1.0
# TODO: Strong typing support for enums, specified but not implemented.
"enums/strong_proto2",
Expand DownExpand Up@@ -162,7 +154,6 @@ _TESTS_TO_SKIP_PLANNER = [
"string_ext/format_errors",

# TODO: Check behavior for go/cpp
"basic/functions/unbound",
"basic/functions/unbound_is_runtime_error",

# TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms.
Expand All@@ -177,14 +168,6 @@ _TESTS_TO_SKIP_PLANNER = [
# Skip until fixed.
"parse/receiver_function_names",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Type inference edgecases around null(able) assignability.
# These type check, but resolve to a different type.
# list(int), want list(wrapper(int))
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,10 +210,10 @@ public void evaluate() throws Throwable {
}

CelRuntime runtime = getRuntime(test, usePlanner);
Program program = runtime.createProgram(response.getAst());
ExprValue result = null;
CelEvaluationException error = null;
try {
Program program = runtime.createProgram(response.getAst());
result = toExprValue(program.eval(getBindings(test)), response.getAst().getResultType());
} catch (CelEvaluationException e) {
error = e;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,11 @@ public void dynamicMessage_dynamicDescriptor() throws Exception {

// All the tests below rely on message creation with fields populated. They are excluded for time
// being until this support is added.
@Override
public void nullAssignability() throws Exception {
skipBaselineVerification();
}

@Override
public void wrappers() throws Exception {
skipBaselineVerification();
Expand Down
35 changes: 35 additions & 0 deletions runtime/src/test/resources/nullAssignability.baseline
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
Source: TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: TestAllTypes{}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)
=====>
bindings: {}
result: false

Source: TestAllTypes{single_value: null}.single_value == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_value: null}.single_value)
=====>
bindings: {}
result: true

Source: TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_timestamp: null}.single_timestamp)
=====>
bindings: {}
result: false

25 changes: 25 additions & 0 deletions testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -2122,6 +2122,31 @@ public void wrappers() throws Exception {
runTest();
}

@Test
public void nullAssignability() throws Exception {
setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()));
source = "TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null";
runTest();

source = "TestAllTypes{}.single_int64_wrapper == null";
runTest();

source = "has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)";
runTest();

source = "TestAllTypes{single_value: null}.single_value == null";
runTest();

source = "has(TestAllTypes{single_value: null}.single_value)";
runTest();

source = "TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)";
runTest();

source = "has(TestAllTypes{single_timestamp: null}.single_timestamp)";
runTest();
}

@Test
public void longComprehension() {
ImmutableList<Long> l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList());
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions common/src/main/java/dev/cel/common/internal/ProtoAdapter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,8 +204,29 @@ public Optional<Object> adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec
@SuppressWarnings({"unchecked", "rawtypes"})
public Optional<Object> adaptValueToFieldType(
FieldDescriptor fieldDescriptor, Object fieldValue) {
if (isWrapperType(fieldDescriptor) && fieldValue.equals(NullValue.NULL_VALUE)) {
return Optional.empty();
if (fieldValue instanceof NullValue) {
// `null` assignment to fields indicate that the field would not be set
// in a protobuf message (e.g: Message{msg_field: null} -> Message{})
//
// We explicitly check below for invalid null assignments, such as repeated
// or map fields. (e.g: Message{repeated_field: null} -> Error)
if (fieldDescriptor.isMapField()
|| fieldDescriptor.isRepeated()
|| fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE
|| WellKnownProto.JSON_STRUCT_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())
|| WellKnownProto.JSON_LIST_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())) {
throw new IllegalArgumentException("Unsupported field type");
}

String typeFullName = fieldDescriptor.getMessageType().getFullName();
if (!WellKnownProto.ANY_VALUE.typeName().equals(typeFullName)
&& !WellKnownProto.JSON_VALUE.typeName().equals(typeFullName)) {
return Optional.empty();
}
}
if (fieldDescriptor.isMapField()) {
Descriptor entryDescriptor = fieldDescriptor.getMessageType();
Expand DownExpand Up@@ -370,14 +391,6 @@ private static String typeName(Descriptor protoType) {
return protoType.getFullName();
}

private static boolean isWrapperType(FieldDescriptor fieldDescriptor) {
if (fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
return false;
}
String fieldTypeName = fieldDescriptor.getMessageType().getFullName();
return WellKnownProto.isWrapperType(fieldTypeName);
}

private static int intCheckedCast(long value) {
try {
return Ints.checkedCast(value);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
try {
unpackedMessage = dynamicProto.unpack((Any) message);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(
throw new IllegalArgumentException(
"Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e);
}
return toRuntimeValue(unpackedMessage);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,10 +150,7 @@ public static List<Object[]> data() {
@Test
public void adaptValueToProto_bidirectionalConversion() {
DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE);
ProtoAdapter protoAdapter =
new ProtoAdapter(
dynamicProto,
CelOptions.current().build());
ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.current().build());
assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName()))
.isEqualTo(proto);
assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value);
Expand DownExpand Up@@ -181,6 +178,18 @@ public void adaptAnyValue_hermeticTypes_bidirectionalConversion() {

@RunWith(JUnit4.class)
public static class AsymmetricConversionTest {

@Test
public void unpackAny_celNullValue() throws Exception {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Any any =
(Any)
protoAdapter.adaptValueToProto(
dev.cel.common.values.NullValue.NULL_VALUE, "google.protobuf.Any");
Object unpacked = protoAdapter.adaptProtoToValue(any);
assertThat(unpacked).isEqualTo(dev.cel.common.values.NullValue.NULL_VALUE);
}

@Test
public void adaptValueToProto_asymmetricFloatConversion() {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Expand Down
17 changes: 0 additions & 17 deletions conformance/src/test/java/dev/cel/conformance/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,14 +124,6 @@ _TESTS_TO_SKIP_LEGACY = [
"string_ext/format",
"string_ext/format_errors",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Future features for CEL 1.0
# TODO: Strong typing support for enums, specified but not implemented.
"enums/strong_proto2",
Expand DownExpand Up@@ -162,7 +154,6 @@ _TESTS_TO_SKIP_PLANNER = [
"string_ext/format_errors",

# TODO: Check behavior for go/cpp
"basic/functions/unbound",
"basic/functions/unbound_is_runtime_error",

# TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms.
Expand All@@ -177,14 +168,6 @@ _TESTS_TO_SKIP_PLANNER = [
# Skip until fixed.
"parse/receiver_function_names",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Type inference edgecases around null(able) assignability.
# These type check, but resolve to a different type.
# list(int), want list(wrapper(int))
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,10 +210,10 @@ public void evaluate() throws Throwable {
}

CelRuntime runtime = getRuntime(test, usePlanner);
Program program = runtime.createProgram(response.getAst());
ExprValue result = null;
CelEvaluationException error = null;
try {
Program program = runtime.createProgram(response.getAst());
result = toExprValue(program.eval(getBindings(test)), response.getAst().getResultType());
} catch (CelEvaluationException e) {
error = e;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,11 @@ public void dynamicMessage_dynamicDescriptor() throws Exception {

// All the tests below rely on message creation with fields populated. They are excluded for time
// being until this support is added.
@Override
public void nullAssignability() throws Exception {
skipBaselineVerification();
}

@Override
public void wrappers() throws Exception {
skipBaselineVerification();
Expand Down
35 changes: 35 additions & 0 deletions runtime/src/test/resources/nullAssignability.baseline
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
Source: TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: TestAllTypes{}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)
=====>
bindings: {}
result: false

Source: TestAllTypes{single_value: null}.single_value == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_value: null}.single_value)
=====>
bindings: {}
result: true

Source: TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_timestamp: null}.single_timestamp)
=====>
bindings: {}
result: false

25 changes: 25 additions & 0 deletions testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -2122,6 +2122,31 @@ public void wrappers() throws Exception {
runTest();
}

@Test
public void nullAssignability() throws Exception {
setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()));
source = "TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null";
runTest();

source = "TestAllTypes{}.single_int64_wrapper == null";
runTest();

source = "has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)";
runTest();

source = "TestAllTypes{single_value: null}.single_value == null";
runTest();

source = "has(TestAllTypes{single_value: null}.single_value)";
runTest();

source = "TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)";
runTest();

source = "has(TestAllTypes{single_timestamp: null}.single_timestamp)";
runTest();
}

@Test
public void longComprehension() {
ImmutableList<Long> l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList());
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions common/src/main/java/dev/cel/common/internal/ProtoAdapter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,8 +204,29 @@ public Optional<Object> adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec
@SuppressWarnings({"unchecked", "rawtypes"})
public Optional<Object> adaptValueToFieldType(
FieldDescriptor fieldDescriptor, Object fieldValue) {
if (isWrapperType(fieldDescriptor) && fieldValue.equals(NullValue.NULL_VALUE)) {
return Optional.empty();
if (fieldValue instanceof NullValue) {
// `null` assignment to fields indicate that the field would not be set
// in a protobuf message (e.g: Message{msg_field: null} -> Message{})
//
// We explicitly check below for invalid null assignments, such as repeated
// or map fields. (e.g: Message{repeated_field: null} -> Error)
if (fieldDescriptor.isMapField()
|| fieldDescriptor.isRepeated()
|| fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE
|| WellKnownProto.JSON_STRUCT_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())
|| WellKnownProto.JSON_LIST_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())) {
throw new IllegalArgumentException("Unsupported field type");
}

String typeFullName = fieldDescriptor.getMessageType().getFullName();
if (!WellKnownProto.ANY_VALUE.typeName().equals(typeFullName)
&& !WellKnownProto.JSON_VALUE.typeName().equals(typeFullName)) {
return Optional.empty();
}
}
if (fieldDescriptor.isMapField()) {
Descriptor entryDescriptor = fieldDescriptor.getMessageType();
Expand DownExpand Up@@ -370,14 +391,6 @@ private static String typeName(Descriptor protoType) {
return protoType.getFullName();
}

private static boolean isWrapperType(FieldDescriptor fieldDescriptor) {
if (fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
return false;
}
String fieldTypeName = fieldDescriptor.getMessageType().getFullName();
return WellKnownProto.isWrapperType(fieldTypeName);
}

private static int intCheckedCast(long value) {
try {
return Ints.checkedCast(value);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
try {
unpackedMessage = dynamicProto.unpack((Any) message);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(
throw new IllegalArgumentException(
"Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e);
}
return toRuntimeValue(unpackedMessage);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,10 +150,7 @@ public static List<Object[]> data() {
@Test
public void adaptValueToProto_bidirectionalConversion() {
DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE);
ProtoAdapter protoAdapter =
new ProtoAdapter(
dynamicProto,
CelOptions.current().build());
ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.current().build());
assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName()))
.isEqualTo(proto);
assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value);
Expand DownExpand Up@@ -181,6 +178,18 @@ public void adaptAnyValue_hermeticTypes_bidirectionalConversion() {

@RunWith(JUnit4.class)
public static class AsymmetricConversionTest {

@Test
public void unpackAny_celNullValue() throws Exception {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Any any =
(Any)
protoAdapter.adaptValueToProto(
dev.cel.common.values.NullValue.NULL_VALUE, "google.protobuf.Any");
Object unpacked = protoAdapter.adaptProtoToValue(any);
assertThat(unpacked).isEqualTo(dev.cel.common.values.NullValue.NULL_VALUE);
}

@Test
public void adaptValueToProto_asymmetricFloatConversion() {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Expand Down
17 changes: 0 additions & 17 deletions conformance/src/test/java/dev/cel/conformance/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,14 +124,6 @@ _TESTS_TO_SKIP_LEGACY = [
"string_ext/format",
"string_ext/format_errors",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Future features for CEL 1.0
# TODO: Strong typing support for enums, specified but not implemented.
"enums/strong_proto2",
Expand DownExpand Up@@ -162,7 +154,6 @@ _TESTS_TO_SKIP_PLANNER = [
"string_ext/format_errors",

# TODO: Check behavior for go/cpp
"basic/functions/unbound",
"basic/functions/unbound_is_runtime_error",

# TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms.
Expand All@@ -177,14 +168,6 @@ _TESTS_TO_SKIP_PLANNER = [
# Skip until fixed.
"parse/receiver_function_names",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Type inference edgecases around null(able) assignability.
# These type check, but resolve to a different type.
# list(int), want list(wrapper(int))
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,10 +210,10 @@ public void evaluate() throws Throwable {
}

CelRuntime runtime = getRuntime(test, usePlanner);
Program program = runtime.createProgram(response.getAst());
ExprValue result = null;
CelEvaluationException error = null;
try {
Program program = runtime.createProgram(response.getAst());
result = toExprValue(program.eval(getBindings(test)), response.getAst().getResultType());
} catch (CelEvaluationException e) {
error = e;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,11 @@ public void dynamicMessage_dynamicDescriptor() throws Exception {

// All the tests below rely on message creation with fields populated. They are excluded for time
// being until this support is added.
@Override
public void nullAssignability() throws Exception {
skipBaselineVerification();
}

@Override
public void wrappers() throws Exception {
skipBaselineVerification();
Expand Down
35 changes: 35 additions & 0 deletions runtime/src/test/resources/nullAssignability.baseline
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
Source: TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: TestAllTypes{}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)
=====>
bindings: {}
result: false

Source: TestAllTypes{single_value: null}.single_value == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_value: null}.single_value)
=====>
bindings: {}
result: true

Source: TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_timestamp: null}.single_timestamp)
=====>
bindings: {}
result: false

25 changes: 25 additions & 0 deletions testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -2122,6 +2122,31 @@ public void wrappers() throws Exception {
runTest();
}

@Test
public void nullAssignability() throws Exception {
setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()));
source = "TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null";
runTest();

source = "TestAllTypes{}.single_int64_wrapper == null";
runTest();

source = "has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)";
runTest();

source = "TestAllTypes{single_value: null}.single_value == null";
runTest();

source = "has(TestAllTypes{single_value: null}.single_value)";
runTest();

source = "TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)";
runTest();

source = "has(TestAllTypes{single_timestamp: null}.single_timestamp)";
runTest();
}

@Test
public void longComprehension() {
ImmutableList<Long> l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList());
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions common/src/main/java/dev/cel/common/internal/ProtoAdapter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,8 +204,29 @@ public Optional<Object> adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec
@SuppressWarnings({"unchecked", "rawtypes"})
public Optional<Object> adaptValueToFieldType(
FieldDescriptor fieldDescriptor, Object fieldValue) {
if (isWrapperType(fieldDescriptor) && fieldValue.equals(NullValue.NULL_VALUE)) {
return Optional.empty();
if (fieldValue instanceof NullValue) {
// `null` assignment to fields indicate that the field would not be set
// in a protobuf message (e.g: Message{msg_field: null} -> Message{})
//
// We explicitly check below for invalid null assignments, such as repeated
// or map fields. (e.g: Message{repeated_field: null} -> Error)
if (fieldDescriptor.isMapField()
|| fieldDescriptor.isRepeated()
|| fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE
|| WellKnownProto.JSON_STRUCT_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())
|| WellKnownProto.JSON_LIST_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())) {
throw new IllegalArgumentException("Unsupported field type");
}

String typeFullName = fieldDescriptor.getMessageType().getFullName();
if (!WellKnownProto.ANY_VALUE.typeName().equals(typeFullName)
&& !WellKnownProto.JSON_VALUE.typeName().equals(typeFullName)) {
return Optional.empty();
}
}
if (fieldDescriptor.isMapField()) {
Descriptor entryDescriptor = fieldDescriptor.getMessageType();
Expand DownExpand Up@@ -370,14 +391,6 @@ private static String typeName(Descriptor protoType) {
return protoType.getFullName();
}

private static boolean isWrapperType(FieldDescriptor fieldDescriptor) {
if (fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
return false;
}
String fieldTypeName = fieldDescriptor.getMessageType().getFullName();
return WellKnownProto.isWrapperType(fieldTypeName);
}

private static int intCheckedCast(long value) {
try {
return Ints.checkedCast(value);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
try {
unpackedMessage = dynamicProto.unpack((Any) message);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(
throw new IllegalArgumentException(
"Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e);
}
return toRuntimeValue(unpackedMessage);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,10 +150,7 @@ public static List<Object[]> data() {
@Test
public void adaptValueToProto_bidirectionalConversion() {
DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE);
ProtoAdapter protoAdapter =
new ProtoAdapter(
dynamicProto,
CelOptions.current().build());
ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.current().build());
assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName()))
.isEqualTo(proto);
assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value);
Expand DownExpand Up@@ -181,6 +178,18 @@ public void adaptAnyValue_hermeticTypes_bidirectionalConversion() {

@RunWith(JUnit4.class)
public static class AsymmetricConversionTest {

@Test
public void unpackAny_celNullValue() throws Exception {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Any any =
(Any)
protoAdapter.adaptValueToProto(
dev.cel.common.values.NullValue.NULL_VALUE, "google.protobuf.Any");
Object unpacked = protoAdapter.adaptProtoToValue(any);
assertThat(unpacked).isEqualTo(dev.cel.common.values.NullValue.NULL_VALUE);
}

@Test
public void adaptValueToProto_asymmetricFloatConversion() {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Expand Down
17 changes: 0 additions & 17 deletions conformance/src/test/java/dev/cel/conformance/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,14 +124,6 @@ _TESTS_TO_SKIP_LEGACY = [
"string_ext/format",
"string_ext/format_errors",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Future features for CEL 1.0
# TODO: Strong typing support for enums, specified but not implemented.
"enums/strong_proto2",
Expand DownExpand Up@@ -162,7 +154,6 @@ _TESTS_TO_SKIP_PLANNER = [
"string_ext/format_errors",

# TODO: Check behavior for go/cpp
"basic/functions/unbound",
"basic/functions/unbound_is_runtime_error",

# TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms.
Expand All@@ -177,14 +168,6 @@ _TESTS_TO_SKIP_PLANNER = [
# Skip until fixed.
"parse/receiver_function_names",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Type inference edgecases around null(able) assignability.
# These type check, but resolve to a different type.
# list(int), want list(wrapper(int))
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,10 +210,10 @@ public void evaluate() throws Throwable {
}

CelRuntime runtime = getRuntime(test, usePlanner);
Program program = runtime.createProgram(response.getAst());
ExprValue result = null;
CelEvaluationException error = null;
try {
Program program = runtime.createProgram(response.getAst());
result = toExprValue(program.eval(getBindings(test)), response.getAst().getResultType());
} catch (CelEvaluationException e) {
error = e;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,11 @@ public void dynamicMessage_dynamicDescriptor() throws Exception {

// All the tests below rely on message creation with fields populated. They are excluded for time
// being until this support is added.
@Override
public void nullAssignability() throws Exception {
skipBaselineVerification();
}

@Override
public void wrappers() throws Exception {
skipBaselineVerification();
Expand Down
35 changes: 35 additions & 0 deletions runtime/src/test/resources/nullAssignability.baseline
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
Source: TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: TestAllTypes{}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)
=====>
bindings: {}
result: false

Source: TestAllTypes{single_value: null}.single_value == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_value: null}.single_value)
=====>
bindings: {}
result: true

Source: TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_timestamp: null}.single_timestamp)
=====>
bindings: {}
result: false

25 changes: 25 additions & 0 deletions testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -2122,6 +2122,31 @@ public void wrappers() throws Exception {
runTest();
}

@Test
public void nullAssignability() throws Exception {
setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()));
source = "TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null";
runTest();

source = "TestAllTypes{}.single_int64_wrapper == null";
runTest();

source = "has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)";
runTest();

source = "TestAllTypes{single_value: null}.single_value == null";
runTest();

source = "has(TestAllTypes{single_value: null}.single_value)";
runTest();

source = "TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)";
runTest();

source = "has(TestAllTypes{single_timestamp: null}.single_timestamp)";
runTest();
}

@Test
public void longComprehension() {
ImmutableList<Long> l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList());
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions common/src/main/java/dev/cel/common/internal/ProtoAdapter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -204,8 +204,29 @@ public Optional<Object> adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec
@SuppressWarnings({"unchecked", "rawtypes"})
public Optional<Object> adaptValueToFieldType(
FieldDescriptor fieldDescriptor, Object fieldValue) {
if (isWrapperType(fieldDescriptor) && fieldValue.equals(NullValue.NULL_VALUE)) {
return Optional.empty();
if (fieldValue instanceof NullValue) {
// `null` assignment to fields indicate that the field would not be set
// in a protobuf message (e.g: Message{msg_field: null} -> Message{})
//
// We explicitly check below for invalid null assignments, such as repeated
// or map fields. (e.g: Message{repeated_field: null} -> Error)
if (fieldDescriptor.isMapField()
|| fieldDescriptor.isRepeated()
|| fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE
|| WellKnownProto.JSON_STRUCT_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())
|| WellKnownProto.JSON_LIST_VALUE
.typeName()
.equals(fieldDescriptor.getMessageType().getFullName())) {
throw new IllegalArgumentException("Unsupported field type");
}

String typeFullName = fieldDescriptor.getMessageType().getFullName();
if (!WellKnownProto.ANY_VALUE.typeName().equals(typeFullName)
&& !WellKnownProto.JSON_VALUE.typeName().equals(typeFullName)) {
return Optional.empty();
}
}
if (fieldDescriptor.isMapField()) {
Descriptor entryDescriptor = fieldDescriptor.getMessageType();
Expand DownExpand Up@@ -370,14 +391,6 @@ private static String typeName(Descriptor protoType) {
return protoType.getFullName();
}

private static boolean isWrapperType(FieldDescriptor fieldDescriptor) {
if (fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
return false;
}
String fieldTypeName = fieldDescriptor.getMessageType().getFullName();
return WellKnownProto.isWrapperType(fieldTypeName);
}

private static int intCheckedCast(long value) {
try {
return Ints.checkedCast(value);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
try {
unpackedMessage = dynamicProto.unpack((Any) message);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(
throw new IllegalArgumentException(
"Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e);
}
return toRuntimeValue(unpackedMessage);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,10 +150,7 @@ public static List<Object[]> data() {
@Test
public void adaptValueToProto_bidirectionalConversion() {
DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE);
ProtoAdapter protoAdapter =
new ProtoAdapter(
dynamicProto,
CelOptions.current().build());
ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.current().build());
assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName()))
.isEqualTo(proto);
assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value);
Expand DownExpand Up@@ -181,6 +178,18 @@ public void adaptAnyValue_hermeticTypes_bidirectionalConversion() {

@RunWith(JUnit4.class)
public static class AsymmetricConversionTest {

@Test
public void unpackAny_celNullValue() throws Exception {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Any any =
(Any)
protoAdapter.adaptValueToProto(
dev.cel.common.values.NullValue.NULL_VALUE, "google.protobuf.Any");
Object unpacked = protoAdapter.adaptProtoToValue(any);
assertThat(unpacked).isEqualTo(dev.cel.common.values.NullValue.NULL_VALUE);
}

@Test
public void adaptValueToProto_asymmetricFloatConversion() {
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
Expand Down
17 changes: 0 additions & 17 deletions conformance/src/test/java/dev/cel/conformance/BUILD.bazel
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,14 +124,6 @@ _TESTS_TO_SKIP_LEGACY = [
"string_ext/format",
"string_ext/format_errors",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Future features for CEL 1.0
# TODO: Strong typing support for enums, specified but not implemented.
"enums/strong_proto2",
Expand DownExpand Up@@ -162,7 +154,6 @@ _TESTS_TO_SKIP_PLANNER = [
"string_ext/format_errors",

# TODO: Check behavior for go/cpp
"basic/functions/unbound",
"basic/functions/unbound_is_runtime_error",

# TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms.
Expand All@@ -177,14 +168,6 @@ _TESTS_TO_SKIP_PLANNER = [
# Skip until fixed.
"parse/receiver_function_names",

# TODO: Fix null assignment to a field
"proto2/set_null/single_message",
"proto2/set_null/single_duration",
"proto2/set_null/single_timestamp",
"proto3/set_null/single_message",
"proto3/set_null/single_duration",
"proto3/set_null/single_timestamp",

# Type inference edgecases around null(able) assignability.
# These type check, but resolve to a different type.
# list(int), want list(wrapper(int))
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -210,10 +210,10 @@ public void evaluate() throws Throwable {
}

CelRuntime runtime = getRuntime(test, usePlanner);
Program program = runtime.createProgram(response.getAst());
ExprValue result = null;
CelEvaluationException error = null;
try {
Program program = runtime.createProgram(response.getAst());
result = toExprValue(program.eval(getBindings(test)), response.getAst().getResultType());
} catch (CelEvaluationException e) {
error = e;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,11 @@ public void dynamicMessage_dynamicDescriptor() throws Exception {

// All the tests below rely on message creation with fields populated. They are excluded for time
// being until this support is added.
@Override
public void nullAssignability() throws Exception {
skipBaselineVerification();
}

@Override
public void wrappers() throws Exception {
skipBaselineVerification();
Expand Down
35 changes: 35 additions & 0 deletions runtime/src/test/resources/nullAssignability.baseline
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
Source: TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: TestAllTypes{}.single_int64_wrapper == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)
=====>
bindings: {}
result: false

Source: TestAllTypes{single_value: null}.single_value == null
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_value: null}.single_value)
=====>
bindings: {}
result: true

Source: TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)
=====>
bindings: {}
result: true

Source: has(TestAllTypes{single_timestamp: null}.single_timestamp)
=====>
bindings: {}
result: false

25 changes: 25 additions & 0 deletions testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -2122,6 +2122,31 @@ public void wrappers() throws Exception {
runTest();
}

@Test
public void nullAssignability() throws Exception {
setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()));
source = "TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null";
runTest();

source = "TestAllTypes{}.single_int64_wrapper == null";
runTest();

source = "has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)";
runTest();

source = "TestAllTypes{single_value: null}.single_value == null";
runTest();

source = "has(TestAllTypes{single_value: null}.single_value)";
runTest();

source = "TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)";
runTest();

source = "has(TestAllTypes{single_timestamp: null}.single_timestamp)";
runTest();
}

@Test
public void longComprehension() {
ImmutableList<Long> l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList());
Expand Down
Loading