From 0fe7e60657a35b15280ec4eed34911bf5d1ddeb0 Mon Sep 17 00:00:00 2001 From: CEL Dev Team Date: Fri, 13 Nov 2026 11:39:53 -0800 Subject: [PATCH] Decouple core modern CEL value representation from legacy V1 evaluation types so pure modern runtimes no longer link deprecated evaluation and interop targets transitively. Previously, the core value library bundled legacy list, map, and struct wrapper bridges directly alongside modern arena-allocated values and aliased the legacy target back into itself. Any binary using modern CEL values was forced to pull in legacy registries, evaluation helpers, and type providers even when no legacy APIs were called. Extracting the legacy wrapper implementations and compatibility adapters into a separate library target, backed by lightweight per-instance dispatch tables registered only when legacy wrappers are constructed, preserves existing interop behavior for unmigrated callers without imposing legacy binary footprint on modern consumers. Removing redundant multiple inheritance from modern container implementations also prevents legacy container interfaces from anchoring unnecessary virtual tables into pure modern binaries. PiperOrigin-RevId: 1021200957 --- common/BUILD | 105 +- common/legacy_value.cc | 1027 +++++++++++++---- common/legacy_value.h | 40 + common/value.cc | 279 +++++ common/values/custom_list_value.cc | 35 +- common/values/custom_map_value.cc | 43 +- common/values/legacy_list_value.cc | 29 +- common/values/legacy_list_value.h | 68 +- common/values/legacy_map_value.cc | 58 +- common/values/legacy_map_value.h | 84 +- common/values/legacy_struct_value.cc | 207 ++++ common/values/legacy_struct_value.h | 146 ++- common/values/list_value_builder.h | 42 - common/values/map_value_builder.h | 42 - common/values/parsed_message_value.h | 5 +- common/values/value_builder.cc | 484 ++------ eval/compiler/flat_expr_builder.cc | 4 + eval/compiler/flat_expr_builder_test.cc | 193 ++++ eval/eval/BUILD | 1 - eval/eval/select_step.cc | 187 +-- eval/eval/select_step_test.cc | 384 ++++++ eval/public/structs/BUILD | 3 + .../cel_proto_wrap_value_to_message.cc | 36 +- .../cel_proto_wrap_value_to_message_test.cc | 32 + extensions/BUILD | 1 + extensions/select_optimization.cc | 6 +- runtime/standard/BUILD | 71 +- runtime/standard/arithmetic_functions.cc | 355 +++--- runtime/standard/comparison_functions.cc | 348 +++--- runtime/standard/container_functions.cc | 108 +- .../container_membership_functions.cc | 444 ++++--- runtime/standard/equality_functions.cc | 393 ++----- runtime/standard/logical_functions.cc | 80 +- runtime/standard/regex_functions.cc | 56 +- runtime/standard/string_functions.cc | 184 +-- runtime/standard/time_functions.cc | 726 +++++------- runtime/standard/type_conversion_functions.cc | 754 ++++++------ 37 files changed, 4185 insertions(+), 2875 deletions(-) diff --git a/common/BUILD b/common/BUILD index f5ca6251f..ae802267a 100644 --- a/common/BUILD +++ b/common/BUILD @@ -727,9 +727,10 @@ cc_library( ], exclude = [ "values/*_test.cc", + "values/legacy_list_value.cc", + "values/legacy_map_value.cc", ], ) + [ - "legacy_value.cc", "value.cc", ], hdrs = glob( @@ -740,7 +741,6 @@ cc_library( "values/*_test.h", ], ) + [ - "legacy_value.h", "type_reflector.h", "value.h", ], @@ -748,7 +748,6 @@ cc_library( ":allocator", ":any", ":arena", - ":kind", ":memory", ":native_type", ":optional_ref", @@ -761,13 +760,6 @@ cc_library( "//base:function_result_set", "//common/internal:byte_string", "//common/internal:unknowns", - "//eval/internal:cel_value_equal", - "//eval/public:cel_value", - "//eval/public:message_wrapper", - "//eval/public/structs:cel_proto_wrap_value_to_message", - "//eval/public/structs:legacy_type_info_apis", - "//eval/public/structs:proto_message_type_adapter", - "//eval/public/structs:trivial_legacy_type_info_internal", "//extensions/protobuf/internal:map_reflection", "//extensions/protobuf/internal:qualify", "//internal:casts", @@ -826,6 +818,7 @@ cc_test( "value_test.cc", ], deps = [ + ":legacy_value", ":memory", ":native_type", ":type", @@ -872,9 +865,97 @@ cc_library( deps = ["//base/internal:unknown_set"], ) -alias( +cc_library( name = "legacy_value", - actual = ":value", + srcs = [ + "legacy_value.cc", + "values/legacy_list_value.cc", + "values/legacy_map_value.cc", + ], + hdrs = glob( + [ + "values/*.h", + ], + exclude = [ + "values/*_test.h", + ], + ) + [ + "legacy_value.h", + "type_reflector.h", + "value.h", + ], + deps = [ + ":allocator", + ":any", + ":arena", + ":kind", + ":memory", + ":native_type", + ":optional_ref", + ":type", + ":typeinfo", + ":unknown", + ":value", + ":value_kind", + "//base:attributes", + "//base:function_result", + "//base:function_result_set", + "//common/internal:byte_string", + "//common/internal:unknowns", + "//eval/internal:cel_value_equal", + "//eval/public:cel_value", + "//eval/public:message_wrapper", + "//eval/public/structs:cel_proto_wrap_value_to_message", + "//eval/public/structs:legacy_type_info_apis", + "//eval/public/structs:proto_message_type_adapter", + "//eval/public/structs:trivial_legacy_type_info_internal", + "//extensions/protobuf/internal:map_reflection", + "//extensions/protobuf/internal:qualify", + "//internal:casts", + "//internal:empty_descriptors", + "//internal:json", + "//internal:manual", + "//internal:message_equality", + "//internal:number", + "//internal:protobuf_runtime_version", + "//internal:status_macros", + "//internal:strings", + "//internal:time", + "//internal:utf8", + "//internal:well_known_types", + "//runtime:runtime_options", + "//runtime/internal:errors", + "@com_google_absl//absl/base", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:no_destructor", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/functional:function_ref", + "@com_google_absl//absl/functional:overload", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/log:absl_check", + "@com_google_absl//absl/log:absl_log", + "@com_google_absl//absl/meta:type_traits", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:cord", + "@com_google_absl//absl/strings:string_view", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:source_location", + "@com_google_absl//absl/types:span", + "@com_google_absl//absl/types:variant", + "@com_google_absl//absl/utility", + "@com_google_protobuf//:any_cc_proto", + "@com_google_protobuf//:duration_cc_proto", + "@com_google_protobuf//:empty_cc_proto", + "@com_google_protobuf//:protobuf", + "@com_google_protobuf//:struct_cc_proto", + "@com_google_protobuf//:timestamp_cc_proto", + "@com_google_protobuf//:wrappers_cc_proto", + "@com_google_protobuf//src/google/protobuf/io", + ], ) cc_library( diff --git a/common/legacy_value.cc b/common/legacy_value.cc index 0ff096ca5..86077d37d 100644 --- a/common/legacy_value.cc +++ b/common/legacy_value.cc @@ -17,12 +17,15 @@ #include #include #include +#include +#include #include #include #include -#include "google/protobuf/struct.pb.h" #include "absl/base/attributes.h" +#include "absl/base/call_once.h" +#include "absl/base/no_destructor.h" #include "absl/base/nullability.h" #include "absl/base/optimization.h" #include "absl/functional/overload.h" @@ -33,19 +36,18 @@ #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" -#include "absl/types/span.h" #include "absl/types/variant.h" #include "base/attribute.h" #include "common/kind.h" #include "common/memory.h" +#include "common/native_type.h" #include "common/type.h" #include "common/unknown.h" #include "common/value.h" #include "common/value_kind.h" #include "common/values/legacy_list_value.h" #include "common/values/legacy_map_value.h" -#include "common/values/list_value_builder.h" -#include "common/values/map_value_builder.h" +#include "common/values/legacy_struct_value.h" #include "common/values/values.h" #include "eval/internal/cel_value_equal.h" #include "eval/public/cel_value.h" @@ -54,6 +56,7 @@ #include "eval/public/structs/legacy_type_info_apis.h" #include "eval/public/structs/proto_message_type_adapter.h" #include "eval/public/structs/trivial_legacy_type_info_internal.h" +#include "internal/casts.h" #include "internal/json.h" #include "internal/status_macros.h" #include "runtime/runtime_options.h" @@ -249,6 +252,480 @@ namespace common_internal { namespace { +class EmptyCompatListValueImpl final : public CompatListValue { + public: + static const EmptyCompatListValueImpl& Get() { + static const absl::NoDestructor empty; + return *empty; + } + + EmptyCompatListValueImpl() = default; + + google::protobuf::Arena* GetOwningArena() const override { return nullptr; } + + std::string DebugString() const override { return "[]"; } + + bool IsEmpty() const override { return true; } + + size_t Size() const override { return 0; } + + absl::Status ConvertToJsonArray( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + ABSL_DCHECK(descriptor_pool != nullptr); + ABSL_DCHECK(message_factory != nullptr); + ABSL_DCHECK(json != nullptr); + ABSL_DCHECK_EQ(json->GetDescriptor()->well_known_type(), + google::protobuf::Descriptor::WELLKNOWNTYPE_LISTVALUE); + + json->Clear(); + return absl::OkStatus(); + } + + CustomListValue Clone(google::protobuf::Arena* absl_nonnull arena) const override { + return CustomListValue(); + } + + int size() const override { return 0; } + + CelValue operator[](int index) const override { + static const absl::NoDestructor error( + absl::InvalidArgumentError("index out of bounds")); + return CelValue::CreateError(&*error); + } + + CelValue Get(google::protobuf::Arena* arena, int index) const override { + if (arena == nullptr) { + return (*this)[index]; + } + return CelValue::CreateError(google::protobuf::Arena::Create( + arena, absl::InvalidArgumentError("index out of bounds"))); + } + + private: + absl::Status Get(size_t index, const google::protobuf::DescriptorPool* absl_nonnull, + google::protobuf::MessageFactory* absl_nonnull, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + *result = IndexOutOfBoundsError(index, arena); + return absl::OkStatus(); + } +}; + +class CompatListValueImpl final : public CompatListValue { + public: + CompatListValueImpl( + CustomListValue value, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) + : value_(std::move(value)), + descriptor_pool_(descriptor_pool), + message_factory_(message_factory), + arena_(arena) { + ABSL_DCHECK(descriptor_pool_ != nullptr); + ABSL_DCHECK(message_factory_ != nullptr); + ABSL_DCHECK(arena_ != nullptr); + } + + google::protobuf::Arena* GetOwningArena() const override { return arena_; } + + std::string DebugString() const override { return value_.DebugString(); } + + absl::Status SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const override { + return value_.SerializeTo(descriptor_pool, message_factory, output); + } + + absl::Status ConvertToJsonArray( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + return value_.ConvertToJsonArray(descriptor_pool, message_factory, json); + } + + absl::Status Equal(const ListValue& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Equal(Value(other), descriptor_pool, message_factory, arena, + result); + } + + bool IsZeroValue() const override { return value_.IsZeroValue(); } + + CustomListValue Clone(google::protobuf::Arena* absl_nonnull arena) const override { + ABSL_DCHECK(arena != nullptr); + if (value_.IsEmpty()) { + return CustomListValue(); + } + return CustomListValue(google::protobuf::Arena::Create( + arena, value_.Clone(arena), descriptor_pool_, + message_factory_, arena), + arena); + } + + bool IsEmpty() const override { return value_.IsEmpty(); } + + size_t Size() const override { return value_.Size(); } + + absl::Status ForEach( + ForEachWithIndexCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + return value_.ForEach(callback, descriptor_pool, message_factory, arena); + } + + absl::StatusOr NewIterator() const override { + return value_.NewIterator(); + } + + absl::Status Contains( + const Value& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Contains(other, descriptor_pool, message_factory, arena, + result); + } + + CelValue operator[](int index) const override { return Get(arena_, index); } + + CelValue Get(google::protobuf::Arena* arena, int index) const override { + if (arena == nullptr) { + arena = arena_; + } + if (ABSL_PREDICT_FALSE(index < 0 || index >= size())) { + return CelValue::CreateError(google::protobuf::Arena::Create( + arena, common_internal::MakeIndexOutOfBoundsError(index))); + } + Value element; + absl::Status status = + value_.Get(static_cast(index), descriptor_pool_, + message_factory_, arena, &element); + if (ABSL_PREDICT_FALSE(!status.ok())) { + return CelValue::CreateError( + google::protobuf::Arena::Create(arena, std::move(status))); + } + return common_internal::UnsafeLegacyValue(element, /*stable=*/false, arena); + } + + int size() const override { return static_cast(Size()); } + + protected: + absl::Status Get(size_t index, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Get(index, descriptor_pool, message_factory, arena, result); + } + + private: + const CustomListValue value_; + const google::protobuf::DescriptorPool* absl_nonnull const descriptor_pool_; + google::protobuf::MessageFactory* absl_nonnull const message_factory_; + google::protobuf::Arena* absl_nonnull const arena_; +}; + +class EmptyCompatMapValueImpl final : public CompatMapValue { + public: + static const EmptyCompatMapValueImpl& Get() { + static const absl::NoDestructor empty; + return *empty; + } + + EmptyCompatMapValueImpl() = default; + + google::protobuf::Arena* GetOwningArena() const override { return nullptr; } + + std::string DebugString() const override { return "{}"; } + + bool IsEmpty() const override { return true; } + + size_t Size() const override { return 0; } + + absl::Status ListKeys( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + ListValue* absl_nonnull result) const override { + *result = ListValue(); + return absl::OkStatus(); + } + + absl::StatusOr NewIterator() const override { + return NewEmptyValueIterator(); + } + + absl::Status ConvertToJsonObject( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + ABSL_DCHECK(descriptor_pool != nullptr); + ABSL_DCHECK(message_factory != nullptr); + ABSL_DCHECK(json != nullptr); + ABSL_DCHECK_EQ(json->GetDescriptor()->well_known_type(), + google::protobuf::Descriptor::WELLKNOWNTYPE_STRUCT); + + json->Clear(); + return absl::OkStatus(); + } + + CustomMapValue Clone(google::protobuf::Arena* absl_nonnull) const override { + return CustomMapValue(); + } + + absl::optional operator[](CelValue key) const override { + return std::nullopt; + } + + using CompatMapValue::Get; + absl::optional Get(google::protobuf::Arena* arena, + CelValue key) const override { + return std::nullopt; + } + + absl::StatusOr Has(const CelValue& key) const override { return false; } + + int size() const override { return static_cast(Size()); } + + absl::StatusOr ListKeys() const override { + return EmptyCompatListValue(); + } + + absl::StatusOr ListKeys(google::protobuf::Arena*) const override { + return ListKeys(); + } + + private: + absl::StatusOr Find( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return false; + } + + absl::StatusOr Has( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + return false; + } +}; + +class CompatMapValueImpl final : public CompatMapValue { + public: + CompatMapValueImpl(CustomMapValue value, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) + : value_(std::move(value)), + descriptor_pool_(descriptor_pool), + message_factory_(message_factory), + arena_(arena) { + ABSL_DCHECK(descriptor_pool_ != nullptr); + ABSL_DCHECK(message_factory_ != nullptr); + ABSL_DCHECK(arena_ != nullptr); + } + + google::protobuf::Arena* GetOwningArena() const override { return arena_; } + + std::string DebugString() const override { return value_.DebugString(); } + + absl::Status SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const override { + return value_.SerializeTo(descriptor_pool, message_factory, output); + } + + absl::Status ConvertToJsonObject( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const override { + return value_.ConvertToJsonObject(descriptor_pool, message_factory, json); + } + + absl::Status Equal(const MapValue& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Equal(Value(other), descriptor_pool, message_factory, arena, + result); + } + + bool IsZeroValue() const override { return value_.IsZeroValue(); } + + CustomMapValue Clone(google::protobuf::Arena* absl_nonnull arena) const override { + ABSL_DCHECK(arena != nullptr); + if (value_.IsEmpty()) { + return CustomMapValue(); + } + return CustomMapValue(google::protobuf::Arena::Create( + arena, value_.Clone(arena), descriptor_pool_, + message_factory_, arena), + arena); + } + + bool IsEmpty() const override { return value_.IsEmpty(); } + + size_t Size() const override { return value_.Size(); } + + absl::Status ListKeys( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + ListValue* absl_nonnull result) const override { + return value_.ListKeys(descriptor_pool, message_factory, arena, result); + } + + absl::Status ForEach( + ForEachCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + return value_.ForEach(callback, descriptor_pool, message_factory, arena); + } + + absl::StatusOr NewIterator() const override { + return value_.NewIterator(); + } + + absl::optional operator[](CelValue key) const override { + return Get(arena_, key); + } + + using CompatMapValue::Get; + absl::optional Get(google::protobuf::Arena* arena, + CelValue key) const override { + if (arena == nullptr) { + arena = arena_; + } + if (auto status = CelValue::CheckMapKeyType(key); !status.ok()) { + status.IgnoreError(); + return std::nullopt; + } + Value modern_key; + if (!ModernValue(arena, key, modern_key).ok()) { + return std::nullopt; + } + Value modern_val; + absl::StatusOr found = value_.Find( + modern_key, descriptor_pool_, message_factory_, arena, &modern_val); + if (!found.ok()) { + return CelValue::CreateError(google::protobuf::Arena::Create( + arena, std::move(found).status())); + } + if (!*found) { + return std::nullopt; + } + return common_internal::UnsafeLegacyValue(modern_val, /*stable=*/false, + arena); + } + + absl::StatusOr Has(const CelValue& key) const override { + CEL_RETURN_IF_ERROR(CelValue::CheckMapKeyType(key)); + Value modern_key; + CEL_RETURN_IF_ERROR(ModernValue(arena_, key, modern_key)); + return Has(modern_key, descriptor_pool_, message_factory_, arena_); + } + + int size() const override { return static_cast(Size()); } + + absl::StatusOr ListKeys() const override { + return ListKeys(arena_); + } + + absl::StatusOr ListKeys(google::protobuf::Arena* arena) const override { + if (arena == nullptr) { + arena = arena_; + } + if (arena == arena_) { + absl::call_once(keys_once_, [this]() { + ListValue modern_keys; + absl::Status status = value_.ListKeys( + descriptor_pool_, message_factory_, arena_, &modern_keys); + if (!status.ok()) { + keys_ = std::move(status); + return; + } + if (auto legacy_list = AsLegacyListValue(modern_keys); + legacy_list.has_value()) { + keys_ = legacy_list->cel_list(); + return; + } + if (auto custom_list = modern_keys.AsCustom(); + custom_list.has_value()) { + keys_ = MakeCompatListValue(*custom_list, descriptor_pool_, + message_factory_, arena_); + return; + } + keys_ = + absl::InternalError("failed to convert list keys to legacy list"); + }); + return keys_; + } + ListValue modern_keys; + CEL_RETURN_IF_ERROR(value_.ListKeys(descriptor_pool_, message_factory_, + arena, &modern_keys)); + if (auto legacy_list = AsLegacyListValue(modern_keys); + legacy_list.has_value()) { + return legacy_list->cel_list(); + } + if (auto custom_list = modern_keys.AsCustom(); custom_list.has_value()) { + return MakeCompatListValue(*custom_list, descriptor_pool_, + message_factory_, arena); + } + return absl::InternalError("failed to convert list keys to legacy list"); + } + + protected: + absl::StatusOr Find( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, + Value* absl_nonnull result) const override { + return value_.Find(key, descriptor_pool, message_factory, arena, result); + } + + absl::StatusOr Has( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const override { + Value result; + CEL_RETURN_IF_ERROR( + value_.Has(key, descriptor_pool, message_factory, arena, &result)); + if (result.IsBool()) { + return result.GetBool().NativeValue(); + } + if (result.IsError()) { + return result.GetError().NativeValue(); + } + return false; + } + + private: + const CustomMapValue value_; + const google::protobuf::DescriptorPool* absl_nonnull const descriptor_pool_; + google::protobuf::MessageFactory* absl_nonnull const message_factory_; + google::protobuf::Arena* absl_nonnull const arena_; + mutable absl::once_flag keys_once_; + mutable absl::StatusOr keys_; +}; + CelValue LegacyTrivialStructValue(google::protobuf::Arena* absl_nonnull arena, const Value& value) { if (auto legacy_struct_value = common_internal::AsLegacyStructValue(value); @@ -367,6 +844,50 @@ LegacyStructValue MakeLegacyStructValue( } // namespace +const CompatListValue* absl_nonnull EmptyCompatListValue() { + return &EmptyCompatListValueImpl::Get(); +} + +absl::StatusOr MakeCompatListValue( + const CustomListValue& value, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) { + ABSL_DCHECK(descriptor_pool != nullptr); + ABSL_DCHECK(message_factory != nullptr); + ABSL_DCHECK(arena != nullptr); + if (value.IsEmpty()) { + return EmptyCompatListValue(); + } + if (value.GetTypeId() == NativeTypeId::For()) { + return cel::internal::down_cast(value.interface()); + } + return google::protobuf::Arena::Create( + arena, value, descriptor_pool, message_factory, arena); +} + +const CompatMapValue* absl_nonnull EmptyCompatMapValue() { + return &EmptyCompatMapValueImpl::Get(); +} + +absl::StatusOr MakeCompatMapValue( + const CustomMapValue& value, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) { + ABSL_DCHECK(descriptor_pool != nullptr); + ABSL_DCHECK(message_factory != nullptr); + ABSL_DCHECK(arena != nullptr); + if (value.IsEmpty()) { + return EmptyCompatMapValue(); + } + if (NativeTypeId::Of(value) == NativeTypeId::For()) { + return cel::internal::down_cast(value.interface()); + } + return google::protobuf::Arena::Create( + arena, value, descriptor_pool, message_factory, arena); +} + google::api::expr::runtime::CelValue UnsafeLegacyValue( const Value& value, bool stable, google::protobuf::Arena* absl_nonnull arena) { switch (value.kind()) { @@ -407,15 +928,18 @@ google::api::expr::runtime::CelValue UnsafeLegacyValue( } } -std::string LegacyListValue::DebugString() const { - return CelValue::CreateList(impl_).DebugString(); +namespace { + +std::string LegacyListDebugString( + const CelList* absl_nullability_unknown impl) { + return CelValue::CreateList(impl).DebugString(); } -// See `ValueInterface::SerializeTo`. -absl::Status LegacyListValue::SerializeTo( +absl::Status LegacyListSerializeTo( + const CelList* absl_nullability_unknown impl, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const { + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) { ABSL_DCHECK(descriptor_pool != nullptr); ABSL_DCHECK(message_factory != nullptr); ABSL_DCHECK(output != nullptr); @@ -430,7 +954,7 @@ absl::Status LegacyListValue::SerializeTo( google::protobuf::Arena arena; const google::protobuf::Message* wrapped = MaybeWrapValueToMessage( - descriptor, message_factory, CelValue::CreateList(impl_), &arena); + descriptor, message_factory, CelValue::CreateList(impl), &arena); if (wrapped == nullptr) { return absl::UnknownError("failed to convert legacy map to JSON"); } @@ -441,114 +965,110 @@ absl::Status LegacyListValue::SerializeTo( return absl::OkStatus(); } -absl::Status LegacyListValue::ConvertToJson( +absl::Status LegacyListConvertToJson( + const CelList* absl_nullability_unknown impl, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Message* absl_nonnull json) const { - { - ABSL_DCHECK(descriptor_pool != nullptr); - ABSL_DCHECK(message_factory != nullptr); - ABSL_DCHECK(json != nullptr); - ABSL_DCHECK_EQ(json->GetDescriptor()->well_known_type(), - google::protobuf::Descriptor::WELLKNOWNTYPE_VALUE); - - google::protobuf::Arena arena; - const google::protobuf::Message* wrapped = - MaybeWrapValueToMessage(json->GetDescriptor(), message_factory, - CelValue::CreateList(impl_), &arena); - if (wrapped == nullptr) { - return absl::UnknownError("failed to convert legacy list to JSON"); - } + google::protobuf::Message* absl_nonnull json) { + ABSL_DCHECK(descriptor_pool != nullptr); + ABSL_DCHECK(message_factory != nullptr); + ABSL_DCHECK(json != nullptr); + ABSL_DCHECK_EQ(json->GetDescriptor()->well_known_type(), + google::protobuf::Descriptor::WELLKNOWNTYPE_VALUE); - if (wrapped->GetDescriptor() == json->GetDescriptor()) { - // We can directly use google::protobuf::Message::Copy(). - json->CopyFrom(*wrapped); - } else { - // Equivalent descriptors but not identical. Must serialize and - // deserialize. - absl::Cord serialized; - if (!wrapped->SerializePartialToString(&serialized)) { - return absl::UnknownError(absl::StrCat("failed to serialize message: ", - wrapped->GetTypeName())); - } - if (!json->ParsePartialFromString(serialized)) { - return absl::UnknownError( - absl::StrCat("failed to parsed message: ", json->GetTypeName())); - } + google::protobuf::Arena arena; + const google::protobuf::Message* wrapped = + MaybeWrapValueToMessage(json->GetDescriptor(), message_factory, + CelValue::CreateList(impl), &arena); + if (wrapped == nullptr) { + return absl::UnknownError("failed to convert legacy list to JSON"); + } + + if (wrapped->GetDescriptor() == json->GetDescriptor()) { + json->CopyFrom(*wrapped); + } else { + absl::Cord serialized; + if (!wrapped->SerializePartialToString(&serialized)) { + return absl::UnknownError(absl::StrCat("failed to serialize message: ", + wrapped->GetTypeName())); + } + if (!json->ParsePartialFromString(serialized)) { + return absl::UnknownError( + absl::StrCat("failed to parsed message: ", json->GetTypeName())); } - return absl::OkStatus(); } + return absl::OkStatus(); } -absl::Status LegacyListValue::ConvertToJsonArray( +absl::Status LegacyListConvertToJsonArray( + const CelList* absl_nullability_unknown impl, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Message* absl_nonnull json) const { - { - ABSL_DCHECK(descriptor_pool != nullptr); - ABSL_DCHECK(message_factory != nullptr); - ABSL_DCHECK(json != nullptr); - ABSL_DCHECK_EQ(json->GetDescriptor()->well_known_type(), - google::protobuf::Descriptor::WELLKNOWNTYPE_LISTVALUE); + google::protobuf::Message* absl_nonnull json) { + ABSL_DCHECK(descriptor_pool != nullptr); + ABSL_DCHECK(message_factory != nullptr); + ABSL_DCHECK(json != nullptr); + ABSL_DCHECK_EQ(json->GetDescriptor()->well_known_type(), + google::protobuf::Descriptor::WELLKNOWNTYPE_LISTVALUE); - google::protobuf::Arena arena; - const google::protobuf::Message* wrapped = - MaybeWrapValueToMessage(json->GetDescriptor(), message_factory, - CelValue::CreateList(impl_), &arena); - if (wrapped == nullptr) { - return absl::UnknownError("failed to convert legacy list to JSON"); - } + google::protobuf::Arena arena; + const google::protobuf::Message* wrapped = + MaybeWrapValueToMessage(json->GetDescriptor(), message_factory, + CelValue::CreateList(impl), &arena); + if (wrapped == nullptr) { + return absl::UnknownError("failed to convert legacy list to JSON"); + } - if (wrapped->GetDescriptor() == json->GetDescriptor()) { - // We can directly use google::protobuf::Message::Copy(). - json->CopyFrom(*wrapped); - } else { - // Equivalent descriptors but not identical. Must serialize and - // deserialize. - absl::Cord serialized; - if (!wrapped->SerializePartialToString(&serialized)) { - return absl::UnknownError(absl::StrCat("failed to serialize message: ", - wrapped->GetTypeName())); - } - if (!json->ParsePartialFromString(serialized)) { - return absl::UnknownError( - absl::StrCat("failed to parsed message: ", json->GetTypeName())); - } + if (wrapped->GetDescriptor() == json->GetDescriptor()) { + json->CopyFrom(*wrapped); + } else { + absl::Cord serialized; + if (!wrapped->SerializePartialToString(&serialized)) { + return absl::UnknownError(absl::StrCat("failed to serialize message: ", + wrapped->GetTypeName())); + } + if (!json->ParsePartialFromString(serialized)) { + return absl::UnknownError( + absl::StrCat("failed to parsed message: ", json->GetTypeName())); } - return absl::OkStatus(); } + return absl::OkStatus(); } -bool LegacyListValue::IsEmpty() const { return impl_->empty(); } +bool LegacyListIsEmpty(const CelList* absl_nullability_unknown impl) { + return impl->empty(); +} -size_t LegacyListValue::Size() const { - return static_cast(impl_->size()); +size_t LegacyListSize(const CelList* absl_nullability_unknown impl) { + return static_cast(impl->size()); } -// See LegacyListValueInterface::Get for documentation. -absl::Status LegacyListValue::Get( - size_t index, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, +absl::Status LegacyListGet( + const CelList* absl_nullability_unknown impl, size_t index, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { - if (ABSL_PREDICT_FALSE(index < 0 || index >= impl_->size())) { + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) { + if (ABSL_PREDICT_FALSE(impl->size() < 0 || + index >= static_cast(impl->size()))) { *result = ErrorValue::From( absl::InvalidArgumentError("index out of bounds"), arena); return absl::OkStatus(); } CEL_RETURN_IF_ERROR( - ModernValue(arena, impl_->Get(arena, static_cast(index)), *result)); + ModernValue(arena, impl->Get(arena, static_cast(index)), *result)); return absl::OkStatus(); } -absl::Status LegacyListValue::ForEach( - ForEachWithIndexCallback callback, +absl::Status LegacyListForEach( + const CelList* absl_nullability_unknown impl, + CustomListValueInterface::ForEachWithIndexCallback callback, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) const { - const auto size = impl_->size(); + google::protobuf::Arena* absl_nonnull arena) { + const auto size = impl->size(); Value element; for (int index = 0; index < size; ++index) { - CEL_RETURN_IF_ERROR(ModernValue(arena, impl_->Get(arena, index), element)); + CEL_RETURN_IF_ERROR(ModernValue(arena, impl->Get(arena, index), element)); CEL_ASSIGN_OR_RETURN(auto ok, callback(index, Value(element))); if (!ok) { break; @@ -557,20 +1077,19 @@ absl::Status LegacyListValue::ForEach( return absl::OkStatus(); } -absl::StatusOr LegacyListValue::NewIterator() - const { - return std::make_unique(impl_); +absl::StatusOr LegacyListNewIterator( + const CelList* absl_nullability_unknown impl) { + return std::make_unique(impl); } -absl::Status LegacyListValue::Contains( - const Value& other, +absl::Status LegacyListContains( + const CelList* absl_nullability_unknown impl, const Value& other, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) { CEL_ASSIGN_OR_RETURN(auto legacy_other, LegacyValue(arena, other)); - const auto* cel_list = impl_; - for (int i = 0; i < cel_list->size(); ++i) { - auto element = cel_list->Get(arena, i); + for (int i = 0; i < impl->size(); ++i) { + auto element = impl->Get(arena, i); absl::optional equal = interop_internal::CelValueEqualImpl(element, legacy_other); // Heterogeneous equality behavior is to just return false if equality @@ -584,14 +1103,28 @@ absl::Status LegacyListValue::Contains( return absl::OkStatus(); } -std::string LegacyMapValue::DebugString() const { - return CelValue::CreateMap(impl_).DebugString(); +constexpr LegacyListValueDispatcher kLegacyListValueDispatcher = { + /*debug_string=*/&LegacyListDebugString, + /*serialize_to=*/&LegacyListSerializeTo, + /*convert_to_json=*/&LegacyListConvertToJson, + /*convert_to_json_array=*/&LegacyListConvertToJsonArray, + /*is_empty=*/&LegacyListIsEmpty, + /*size=*/&LegacyListSize, + /*get=*/&LegacyListGet, + /*for_each=*/&LegacyListForEach, + /*new_iterator=*/&LegacyListNewIterator, + /*contains=*/&LegacyListContains, +}; + +std::string LegacyMapDebugString(const CelMap* absl_nullability_unknown impl) { + return CelValue::CreateMap(impl).DebugString(); } -absl::Status LegacyMapValue::SerializeTo( +absl::Status LegacyMapSerializeTo( + const CelMap* absl_nullability_unknown impl, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const { + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) { ABSL_DCHECK(descriptor_pool != nullptr); ABSL_DCHECK(message_factory != nullptr); ABSL_DCHECK(output != nullptr); @@ -605,7 +1138,7 @@ absl::Status LegacyMapValue::SerializeTo( google::protobuf::Arena arena; const google::protobuf::Message* wrapped = MaybeWrapValueToMessage( - descriptor, message_factory, CelValue::CreateMap(impl_), &arena); + descriptor, message_factory, CelValue::CreateMap(impl), &arena); if (wrapped == nullptr) { return absl::UnknownError("failed to convert legacy map to JSON"); } @@ -616,10 +1149,11 @@ absl::Status LegacyMapValue::SerializeTo( return absl::OkStatus(); } -absl::Status LegacyMapValue::ConvertToJson( +absl::Status LegacyMapConvertToJson( + const CelMap* absl_nullability_unknown impl, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Message* absl_nonnull json) const { + google::protobuf::Message* absl_nonnull json) { ABSL_DCHECK(descriptor_pool != nullptr); ABSL_DCHECK(message_factory != nullptr); ABSL_DCHECK(json != nullptr); @@ -629,16 +1163,14 @@ absl::Status LegacyMapValue::ConvertToJson( google::protobuf::Arena arena; const google::protobuf::Message* wrapped = MaybeWrapValueToMessage(json->GetDescriptor(), message_factory, - CelValue::CreateMap(impl_), &arena); + CelValue::CreateMap(impl), &arena); if (wrapped == nullptr) { return absl::UnknownError("failed to convert legacy map to JSON"); } if (wrapped->GetDescriptor() == json->GetDescriptor()) { - // We can directly use google::protobuf::Message::Copy(). json->CopyFrom(*wrapped); } else { - // Equivalent descriptors but not identical. Must serialize and deserialize. absl::Cord serialized; if (!wrapped->SerializePartialToString(&serialized)) { return absl::UnknownError(absl::StrCat("failed to serialize message: ", @@ -652,10 +1184,11 @@ absl::Status LegacyMapValue::ConvertToJson( return absl::OkStatus(); } -absl::Status LegacyMapValue::ConvertToJsonObject( +absl::Status LegacyMapConvertToJsonObject( + const CelMap* absl_nullability_unknown impl, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Message* absl_nonnull json) const { + google::protobuf::Message* absl_nonnull json) { ABSL_DCHECK(descriptor_pool != nullptr); ABSL_DCHECK(message_factory != nullptr); ABSL_DCHECK(json != nullptr); @@ -665,16 +1198,14 @@ absl::Status LegacyMapValue::ConvertToJsonObject( google::protobuf::Arena arena; const google::protobuf::Message* wrapped = MaybeWrapValueToMessage(json->GetDescriptor(), message_factory, - CelValue::CreateMap(impl_), &arena); + CelValue::CreateMap(impl), &arena); if (wrapped == nullptr) { return absl::UnknownError("failed to convert legacy map to JSON"); } if (wrapped->GetDescriptor() == json->GetDescriptor()) { - // We can directly use google::protobuf::Message::Copy(). json->CopyFrom(*wrapped); } else { - // Equivalent descriptors but not identical. Must serialize and deserialize. absl::Cord serialized; if (!wrapped->SerializePartialToString(&serialized)) { return absl::UnknownError(absl::StrCat("failed to serialize message: ", @@ -688,17 +1219,19 @@ absl::Status LegacyMapValue::ConvertToJsonObject( return absl::OkStatus(); } -bool LegacyMapValue::IsEmpty() const { return impl_->empty(); } +bool LegacyMapIsEmpty(const CelMap* absl_nullability_unknown impl) { + return impl->empty(); +} -size_t LegacyMapValue::Size() const { - return static_cast(impl_->size()); +size_t LegacyMapSize(const CelMap* absl_nullability_unknown impl) { + return static_cast(impl->size()); } -absl::Status LegacyMapValue::Get( - const Value& key, +absl::Status LegacyMapGet( + const CelMap* absl_nullability_unknown impl, const Value& key, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) { switch (key.kind()) { case ValueKind::kError: ABSL_FALLTHROUGH_INTENDED; @@ -718,7 +1251,7 @@ absl::Status LegacyMapValue::Get( return absl::OkStatus(); } CEL_ASSIGN_OR_RETURN(auto cel_key, LegacyValue(arena, key)); - auto cel_value = impl_->Get(arena, cel_key); + auto cel_value = impl->Get(arena, cel_key); if (!cel_value.has_value()) { *result = NoSuchKeyError(key.DebugString(), arena); return absl::OkStatus(); @@ -727,11 +1260,11 @@ absl::Status LegacyMapValue::Get( return absl::OkStatus(); } -absl::StatusOr LegacyMapValue::Find( - const Value& key, +absl::StatusOr LegacyMapFind( + const CelMap* absl_nullability_unknown impl, const Value& key, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) { switch (key.kind()) { case ValueKind::kError: ABSL_FALLTHROUGH_INTENDED; @@ -750,7 +1283,7 @@ absl::StatusOr LegacyMapValue::Find( *result = ErrorValue::From(InvalidMapKeyTypeError(key.kind()), arena); } CEL_ASSIGN_OR_RETURN(auto cel_key, LegacyValue(arena, key)); - auto cel_value = impl_->Get(arena, cel_key); + auto cel_value = impl->Get(arena, cel_key); if (!cel_value.has_value()) { *result = NullValue{}; return false; @@ -759,11 +1292,11 @@ absl::StatusOr LegacyMapValue::Find( return true; } -absl::Status LegacyMapValue::Has( - const Value& key, +absl::Status LegacyMapHas( + const CelMap* absl_nullability_unknown impl, const Value& key, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) { switch (key.kind()) { case ValueKind::kError: ABSL_FALLTHROUGH_INTENDED; @@ -783,7 +1316,7 @@ absl::Status LegacyMapValue::Has( return absl::OkStatus(); } CEL_ASSIGN_OR_RETURN(auto cel_key, LegacyValue(arena, key)); - absl::StatusOr has = impl_->Has(cel_key); + absl::StatusOr has = impl->Has(cel_key); if (!has.ok()) { *result = ErrorValue::From(std::move(has).status(), arena); return absl::OkStatus(); @@ -793,29 +1326,34 @@ absl::Status LegacyMapValue::Has( return absl::OkStatus(); } -absl::Status LegacyMapValue::ListKeys( +absl::Status LegacyMapListKeys( + const CelMap* absl_nullability_unknown impl, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, ListValue* absl_nonnull result) const { - CEL_ASSIGN_OR_RETURN(auto keys, impl_->ListKeys(arena)); + google::protobuf::Arena* absl_nonnull arena, ListValue* absl_nonnull result) { + CEL_ASSIGN_OR_RETURN(auto keys, impl->ListKeys(arena)); *result = ListValue{common_internal::LegacyListValue(keys)}; return absl::OkStatus(); } -absl::Status LegacyMapValue::ForEach( - ForEachCallback callback, +absl::Status LegacyMapForEach( + const CelMap* absl_nullability_unknown impl, + CustomMapValueInterface::ForEachCallback callback, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) const { - CEL_ASSIGN_OR_RETURN(auto keys, impl_->ListKeys(arena)); + google::protobuf::Arena* absl_nonnull arena) { + CEL_ASSIGN_OR_RETURN(auto keys, impl->ListKeys(arena)); const auto size = keys->size(); Value key; Value value; for (int index = 0; index < size; ++index) { auto cel_key = keys->Get(arena, index); - auto cel_value = *impl_->Get(arena, cel_key); + absl::optional cel_value = impl->Get(arena, cel_key); + if (!cel_value.has_value()) { + return absl::NotFoundError("Key from ListKeys not found in CelMap"); + } CEL_RETURN_IF_ERROR(ModernValue(arena, cel_key, key)); - CEL_RETURN_IF_ERROR(ModernValue(arena, cel_value, value)); + CEL_RETURN_IF_ERROR(ModernValue(arena, *cel_value, value)); CEL_ASSIGN_OR_RETURN(auto ok, callback(key, value)); if (!ok) { break; @@ -824,87 +1362,115 @@ absl::Status LegacyMapValue::ForEach( return absl::OkStatus(); } -absl::StatusOr LegacyMapValue::NewIterator() - const { - return std::make_unique(impl_); +absl::StatusOr LegacyMapNewIterator( + const CelMap* absl_nullability_unknown impl) { + return std::make_unique(impl); } -absl::string_view LegacyStructValue::GetTypeName() const { - auto message_wrapper = AsMessageWrapper(message_ptr_, legacy_type_info_); +constexpr LegacyMapValueDispatcher kLegacyMapValueDispatcher = { + /*debug_string=*/&LegacyMapDebugString, + /*serialize_to=*/&LegacyMapSerializeTo, + /*convert_to_json=*/&LegacyMapConvertToJson, + /*convert_to_json_object=*/&LegacyMapConvertToJsonObject, + /*is_empty=*/&LegacyMapIsEmpty, + /*size=*/&LegacyMapSize, + /*get=*/&LegacyMapGet, + /*find=*/&LegacyMapFind, + /*has=*/&LegacyMapHas, + /*list_keys=*/&LegacyMapListKeys, + /*for_each=*/&LegacyMapForEach, + /*new_iterator=*/&LegacyMapNewIterator, +}; + +absl::string_view LegacyStructGetTypeName( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info) { + auto message_wrapper = AsMessageWrapper(message_ptr, legacy_type_info); return message_wrapper.legacy_type_info()->GetTypename(message_wrapper); } -std::string LegacyStructValue::DebugString() const { - auto message_wrapper = AsMessageWrapper(message_ptr_, legacy_type_info_); +std::string LegacyStructDebugString( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info) { + auto message_wrapper = AsMessageWrapper(message_ptr, legacy_type_info); return message_wrapper.legacy_type_info()->DebugString(message_wrapper); } -absl::Status LegacyStructValue::SerializeTo( +absl::Status LegacyStructSerializeTo( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const { + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) { ABSL_DCHECK(descriptor_pool != nullptr); ABSL_DCHECK(message_factory != nullptr); ABSL_DCHECK(output != nullptr); if (ABSL_PREDICT_TRUE( - message_ptr_->SerializePartialToZeroCopyStream(output))) { + message_ptr->SerializePartialToZeroCopyStream(output))) { return absl::OkStatus(); } return absl::UnknownError("failed to serialize protocol buffer message"); } -absl::Status LegacyStructValue::ConvertToJson( +absl::Status LegacyStructConvertToJson( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Message* absl_nonnull json) const { - ABSL_DCHECK(message_ptr_ != nullptr); + google::protobuf::Message* absl_nonnull json) { + ABSL_DCHECK(message_ptr != nullptr); ABSL_DCHECK(descriptor_pool != nullptr); ABSL_DCHECK(message_factory != nullptr); ABSL_DCHECK(json != nullptr); ABSL_DCHECK_EQ(json->GetDescriptor()->well_known_type(), google::protobuf::Descriptor::WELLKNOWNTYPE_VALUE); - return internal::MessageToJson(*message_ptr_, descriptor_pool, - message_factory, json); + return internal::MessageToJson(*message_ptr, descriptor_pool, message_factory, + json); } -absl::Status LegacyStructValue::ConvertToJsonObject( +absl::Status LegacyStructConvertToJsonObject( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Message* absl_nonnull json) const { - ABSL_DCHECK(message_ptr_ != nullptr); + google::protobuf::Message* absl_nonnull json) { + ABSL_DCHECK(message_ptr != nullptr); ABSL_DCHECK(descriptor_pool != nullptr); ABSL_DCHECK(message_factory != nullptr); ABSL_DCHECK(json != nullptr); ABSL_DCHECK_EQ(json->GetDescriptor()->well_known_type(), google::protobuf::Descriptor::WELLKNOWNTYPE_STRUCT); - return internal::MessageToJson(*message_ptr_, descriptor_pool, - message_factory, json); + return internal::MessageToJson(*message_ptr, descriptor_pool, message_factory, + json); } -absl::Status LegacyStructValue::Equal( +absl::Status LegacyStructEqual( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, const Value& other, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { - ABSL_DCHECK(message_ptr_ != nullptr); - if (ABSL_PREDICT_FALSE(legacy_type_info_ == TrivialTypeInfo::GetInstance())) { + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) { + ABSL_DCHECK(message_ptr != nullptr); + if (ABSL_PREDICT_FALSE(legacy_type_info == TrivialTypeInfo::GetInstance())) { return absl::UnimplementedError( - absl::StrCat("legacy access APIs missing for ", GetTypeName())); + absl::StrCat("legacy access APIs missing for ", + LegacyStructGetTypeName(message_ptr, legacy_type_info))); } - auto modern_value = UnsafeParsedMessageValue(message_ptr_); + auto modern_value = UnsafeParsedMessageValue(message_ptr); // Unwrap the rhs if it's a legacy struct so the normal implementation // doesn't hit the fallback abstract struct comparison. if (auto other_legacy = common_internal::AsLegacyStructValue(other); other_legacy) { - if (other_legacy->legacy_type_info_ == TrivialTypeInfo::GetInstance()) { + if (other_legacy->legacy_type_info() == TrivialTypeInfo::GetInstance()) { return absl::UnimplementedError(absl::StrCat( "legacy access APIs missing for ", other_legacy->GetTypeName())); } - auto other_message = UnsafeParsedMessageValue(other_legacy->message_ptr_); + auto other_message = UnsafeParsedMessageValue(other_legacy->message_ptr()); return modern_value.Equal(other_message, descriptor_pool, message_factory, arena, result); } @@ -913,25 +1479,29 @@ absl::Status LegacyStructValue::Equal( result); } -bool LegacyStructValue::IsZeroValue() const { - ABSL_DCHECK(message_ptr_ != nullptr); - if (ABSL_PREDICT_FALSE(legacy_type_info_ == TrivialTypeInfo::GetInstance())) { +bool LegacyStructIsZeroValue( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info) { + ABSL_DCHECK(message_ptr != nullptr); + if (ABSL_PREDICT_FALSE(legacy_type_info == TrivialTypeInfo::GetInstance())) { return false; } - return UnsafeParsedMessageValue(message_ptr_).IsZeroValue(); + return UnsafeParsedMessageValue(message_ptr).IsZeroValue(); } -absl::Status LegacyStructValue::GetFieldByName( +absl::Status LegacyStructGetFieldByName( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, absl::string_view name, ProtoWrapperTypeOptions unboxing_options, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { - if (ABSL_PREDICT_FALSE(legacy_type_info_ == TrivialTypeInfo::GetInstance())) { + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) { + if (ABSL_PREDICT_FALSE(legacy_type_info == TrivialTypeInfo::GetInstance())) { *result = NoSuchFieldError(name, arena); return absl::OkStatus(); } - ParsedMessageValue parsed_message = UnsafeParsedMessageValue(message_ptr_); + ParsedMessageValue parsed_message = UnsafeParsedMessageValue(message_ptr); const auto* descriptor = parsed_message.GetDescriptor(); const auto* field = descriptor->FindFieldByName(name); if (field == nullptr) { @@ -944,60 +1514,72 @@ absl::Status LegacyStructValue::GetFieldByName( } return interop_internal::WrapLegacyMessageField( - message_ptr_, field, unboxing_options, descriptor_pool, message_factory, + message_ptr, field, unboxing_options, descriptor_pool, message_factory, arena, result); } -absl::Status LegacyStructValue::GetFieldByNumber( +absl::Status LegacyStructGetFieldByNumber( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, int64_t number, ProtoWrapperTypeOptions unboxing_options, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) { return absl::UnimplementedError( "access to fields by numbers is not available for legacy structs"); } -absl::StatusOr LegacyStructValue::HasFieldByName( - absl::string_view name) const { - ABSL_DCHECK(message_ptr_ != nullptr); - if (ABSL_PREDICT_FALSE(legacy_type_info_ == TrivialTypeInfo::GetInstance())) { +absl::StatusOr LegacyStructHasFieldByName( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + absl::string_view name) { + ABSL_DCHECK(message_ptr != nullptr); + if (ABSL_PREDICT_FALSE(legacy_type_info == TrivialTypeInfo::GetInstance())) { return common_internal::MakeNoSuchFieldError(name); } - return UnsafeParsedMessageValue(message_ptr_).HasFieldByName(name); + return UnsafeParsedMessageValue(message_ptr).HasFieldByName(name); } -absl::StatusOr LegacyStructValue::HasFieldByNumber(int64_t number) const { - ABSL_DCHECK(message_ptr_ != nullptr); - if (ABSL_PREDICT_FALSE(legacy_type_info_ == TrivialTypeInfo::GetInstance())) { +absl::StatusOr LegacyStructHasFieldByNumber( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + int64_t number) { + ABSL_DCHECK(message_ptr != nullptr); + if (ABSL_PREDICT_FALSE(legacy_type_info == TrivialTypeInfo::GetInstance())) { return common_internal::MakeNoSuchFieldError(absl::StrCat(number)); } - return UnsafeParsedMessageValue(message_ptr_).HasFieldByNumber(number); + return UnsafeParsedMessageValue(message_ptr).HasFieldByNumber(number); } -absl::Status LegacyStructValue::ForEachField( - ForEachFieldCallback callback, +absl::Status LegacyStructForEachField( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + CustomStructValueInterface::ForEachFieldCallback callback, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) const { - ABSL_DCHECK(message_ptr_ != nullptr); - if (ABSL_PREDICT_FALSE(legacy_type_info_ == TrivialTypeInfo::GetInstance())) { + google::protobuf::Arena* absl_nonnull arena) { + ABSL_DCHECK(message_ptr != nullptr); + if (ABSL_PREDICT_FALSE(legacy_type_info == TrivialTypeInfo::GetInstance())) { return absl::UnimplementedError( - absl::StrCat("legacy access APIs missing for ", GetTypeName())); + absl::StrCat("legacy access APIs missing for ", + LegacyStructGetTypeName(message_ptr, legacy_type_info))); } - return UnsafeParsedMessageValue(message_ptr_) + return UnsafeParsedMessageValue(message_ptr) .ForEachField(callback, descriptor_pool, message_factory, arena); } -absl::Status LegacyStructValue::Qualify( +absl::Status LegacyStructQualify( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, absl::Span qualifiers, bool presence_test, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, google::protobuf::MessageFactory* absl_nonnull message_factory, google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result, - int* absl_nonnull count) const { + int* absl_nonnull count) { if (ABSL_PREDICT_FALSE(qualifiers.empty())) { return absl::InvalidArgumentError("invalid select qualifier path."); } - if (ABSL_PREDICT_FALSE(legacy_type_info_ == TrivialTypeInfo::GetInstance())) { + if (ABSL_PREDICT_FALSE(legacy_type_info == TrivialTypeInfo::GetInstance())) { absl::string_view field_name = absl::visit( absl::Overload( [](const FieldSpecifier& field) -> absl::string_view { @@ -1012,7 +1594,7 @@ absl::Status LegacyStructValue::Qualify( return absl::OkStatus(); } - ParsedMessageValue parsed_message = UnsafeParsedMessageValue(message_ptr_); + ParsedMessageValue parsed_message = UnsafeParsedMessageValue(message_ptr); CEL_RETURN_IF_ERROR(parsed_message.Qualify(qualifiers, presence_test, descriptor_pool, message_factory, arena, result, count)); @@ -1021,6 +1603,57 @@ absl::Status LegacyStructValue::Qualify( return absl::OkStatus(); } +const google::protobuf::Message* absl_nullable LegacyStructGetLegacyMessage( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info) { + if (message_ptr == nullptr || legacy_type_info == nullptr || + legacy_type_info == TrivialTypeInfo::GetInstance()) { + return nullptr; + } + // This should not be possible using the normal public APIs, but possible + // if someone used the MessageWrapper class directly. + if (IsWellKnownMessageType(message_ptr->GetDescriptor())) { + return nullptr; + } + return message_ptr; +} + +constexpr LegacyStructValueDispatcher kLegacyStructValueDispatcher = { + /*get_type_name=*/&LegacyStructGetTypeName, + /*debug_string=*/&LegacyStructDebugString, + /*serialize_to=*/&LegacyStructSerializeTo, + /*convert_to_json=*/&LegacyStructConvertToJson, + /*convert_to_json_object=*/&LegacyStructConvertToJsonObject, + /*equal=*/&LegacyStructEqual, + /*is_zero_value=*/&LegacyStructIsZeroValue, + /*get_field_by_name=*/&LegacyStructGetFieldByName, + /*get_field_by_number=*/&LegacyStructGetFieldByNumber, + /*has_field_by_name=*/&LegacyStructHasFieldByName, + /*has_field_by_number=*/&LegacyStructHasFieldByNumber, + /*for_each_field=*/&LegacyStructForEachField, + /*qualify=*/&LegacyStructQualify, + /*get_legacy_message=*/&LegacyStructGetLegacyMessage, + /*wrap_legacy_message_field=*/&interop_internal::WrapLegacyMessageField, +}; + +} // namespace + +LegacyListValue::LegacyListValue( + const google::api::expr::runtime::CelList* absl_nullability_unknown impl) + : dispatcher_(&kLegacyListValueDispatcher), impl_(impl) {} + +LegacyMapValue::LegacyMapValue( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl) + : dispatcher_(&kLegacyMapValueDispatcher), impl_(impl) {} + +LegacyStructValue::LegacyStructValue( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info) + : dispatcher_(&kLegacyStructValueDispatcher), + message_ptr_(message_ptr), + legacy_type_info_(legacy_type_info) {} + } // namespace common_internal absl::Status ModernValue(google::protobuf::Arena* arena, @@ -1288,6 +1921,12 @@ void WrapLegacyFieldAccessResult(google::protobuf::Arena* absl_nonnull arena, *result = WrapLegacyParsedMapField(result->GetParsedMapField(), arena); } else if (result->IsParsedJsonMap()) { *result = WrapLegacyParsedJsonMap(result->GetParsedJsonMap(), arena); + } else if (result->IsCustomList() && result->GetCustomList().interface() == + CustomListValue().interface()) { + *result = CustomListValue(common_internal::EmptyCompatListValue(), arena); + } else if (result->IsCustomMap() && result->GetCustomMap().interface() == + CustomMapValue().interface()) { + *result = CustomMapValue(common_internal::EmptyCompatMapValue(), arena); } } diff --git a/common/legacy_value.h b/common/legacy_value.h index 8fea22ec0..b241a8b81 100644 --- a/common/legacy_value.h +++ b/common/legacy_value.h @@ -24,12 +24,14 @@ #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "absl/types/span.h" +#include "common/native_type.h" #include "common/value.h" #include "eval/public/cel_value.h" #include "internal/status_macros.h" #include "runtime/runtime_options.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" +#include "google/protobuf/message.h" namespace cel { @@ -48,6 +50,44 @@ absl::StatusOr LegacyValue( namespace common_internal { +class CompatListValue : public CustomListValueInterface, + public google::api::expr::runtime::CelList { + public: + virtual google::protobuf::Arena* GetOwningArena() const = 0; + + private: + NativeTypeId GetNativeTypeId() const final { + return NativeTypeId::For(); + } +}; + +const CompatListValue* absl_nonnull EmptyCompatListValue(); + +absl::StatusOr MakeCompatListValue( + const CustomListValue& value, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena); + +class CompatMapValue : public CustomMapValueInterface, + public google::api::expr::runtime::CelMap { + public: + virtual google::protobuf::Arena* GetOwningArena() const = 0; + + private: + NativeTypeId GetNativeTypeId() const final { + return NativeTypeId::For(); + } +}; + +const CompatMapValue* absl_nonnull EmptyCompatMapValue(); + +absl::StatusOr MakeCompatMapValue( + const CustomMapValue& value, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena); + // Convert a `cel::Value` to `google::api::expr::runtime::CelValue`, using // `arena` to make memory allocations if necessary. `stable` indicates whether // `cel::Value` is in a location where it will not be moved, so that inline diff --git a/common/value.cc b/common/value.cc index bc8a3d7a0..571767d0d 100644 --- a/common/value.cc +++ b/common/value.cc @@ -57,6 +57,7 @@ #include "runtime/runtime_options.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" +#include "google/protobuf/io/zero_copy_stream.h" #include "google/protobuf/message.h" #undef GetMessage @@ -2378,4 +2379,282 @@ StringValue::StringValue(const BytesValue& other) : StringValue(other.value_) { }))); } +namespace common_internal { + +std::string LegacyListValue::DebugString() const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return "[]"; + } + return dispatcher_->debug_string(impl_); +} + +absl::Status LegacyListValue::SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy list value support is not linked"); + } + return dispatcher_->serialize_to(impl_, descriptor_pool, message_factory, + output); +} + +absl::Status LegacyListValue::ConvertToJson( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy list value support is not linked"); + } + return dispatcher_->convert_to_json(impl_, descriptor_pool, message_factory, + json); +} + +absl::Status LegacyListValue::ConvertToJsonArray( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy list value support is not linked"); + } + return dispatcher_->convert_to_json_array(impl_, descriptor_pool, + message_factory, json); +} + +absl::Status LegacyListValue::Equal( + const Value& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy list value support is not linked"); + } + if (auto list_value = other.AsList(); list_value.has_value()) { + return ListValueEqual(*this, *list_value, descriptor_pool, message_factory, + arena, result); + } + *result = FalseValue(); + return absl::OkStatus(); +} + +bool LegacyListValue::IsEmpty() const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return true; + } + return dispatcher_->is_empty(impl_); +} + +size_t LegacyListValue::Size() const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return 0; + } + return dispatcher_->size(impl_); +} + +absl::Status LegacyListValue::Get( + size_t index, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy list value support is not linked"); + } + return dispatcher_->get(impl_, index, descriptor_pool, message_factory, arena, + result); +} + +absl::Status LegacyListValue::ForEach( + ForEachWithIndexCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy list value support is not linked"); + } + return dispatcher_->for_each(impl_, callback, descriptor_pool, + message_factory, arena); +} + +absl::StatusOr LegacyListValue::NewIterator() + const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy list value support is not linked"); + } + return dispatcher_->new_iterator(impl_); +} + +absl::Status LegacyListValue::Contains( + const Value& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy list value support is not linked"); + } + return dispatcher_->contains(impl_, other, descriptor_pool, message_factory, + arena, result); +} + +std::string LegacyMapValue::DebugString() const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return "{}"; + } + return dispatcher_->debug_string(impl_); +} + +absl::Status LegacyMapValue::SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy map value support is not linked"); + } + return dispatcher_->serialize_to(impl_, descriptor_pool, message_factory, + output); +} + +absl::Status LegacyMapValue::ConvertToJson( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy map value support is not linked"); + } + return dispatcher_->convert_to_json(impl_, descriptor_pool, message_factory, + json); +} + +absl::Status LegacyMapValue::ConvertToJsonObject( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy map value support is not linked"); + } + return dispatcher_->convert_to_json_object(impl_, descriptor_pool, + message_factory, json); +} + +absl::Status LegacyMapValue::Equal( + const Value& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy map value support is not linked"); + } + if (auto map_value = other.AsMap(); map_value.has_value()) { + return MapValueEqual(*this, *map_value, descriptor_pool, message_factory, + arena, result); + } + *result = FalseValue(); + return absl::OkStatus(); +} + +bool LegacyMapValue::IsEmpty() const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return true; + } + return dispatcher_->is_empty(impl_); +} + +size_t LegacyMapValue::Size() const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return 0; + } + return dispatcher_->size(impl_); +} + +absl::Status LegacyMapValue::Get( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy map value support is not linked"); + } + return dispatcher_->get(impl_, key, descriptor_pool, message_factory, arena, + result); +} + +absl::StatusOr LegacyMapValue::Find( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy map value support is not linked"); + } + return dispatcher_->find(impl_, key, descriptor_pool, message_factory, arena, + result); +} + +absl::Status LegacyMapValue::Has( + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy map value support is not linked"); + } + return dispatcher_->has(impl_, key, descriptor_pool, message_factory, arena, + result); +} + +absl::Status LegacyMapValue::ListKeys( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, ListValue* absl_nonnull result) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy map value support is not linked"); + } + return dispatcher_->list_keys(impl_, descriptor_pool, message_factory, arena, + result); +} + +absl::Status LegacyMapValue::ForEach( + ForEachCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy map value support is not linked"); + } + return dispatcher_->for_each(impl_, callback, descriptor_pool, + message_factory, arena); +} + +absl::StatusOr LegacyMapValue::NewIterator() + const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError("legacy map value support is not linked"); + } + return dispatcher_->new_iterator(impl_); +} + +} // namespace common_internal + } // namespace cel diff --git a/common/values/custom_list_value.cc b/common/values/custom_list_value.cc index 5a34b42cf..1e59ab1fe 100644 --- a/common/values/custom_list_value.cc +++ b/common/values/custom_list_value.cc @@ -26,9 +26,7 @@ #include "absl/strings/string_view.h" #include "common/native_type.h" #include "common/value.h" -#include "common/values/list_value_builder.h" #include "common/values/values.h" -#include "eval/public/cel_value.h" #include "internal/status_macros.h" #include "internal/well_known_types.h" #include "google/protobuf/arena.h" @@ -42,9 +40,8 @@ namespace { using ::cel::well_known_types::ListValueReflection; using ::cel::well_known_types::ValueReflection; -using ::google::api::expr::runtime::CelValue; -class EmptyListValue final : public common_internal::CompatListValue { +class EmptyListValue final : public CustomListValueInterface { public: static const EmptyListValue& Get() { static const absl::NoDestructor empty; @@ -53,8 +50,6 @@ class EmptyListValue final : public common_internal::CompatListValue { EmptyListValue() = default; - google::protobuf::Arena* GetOwningArena() const override { return nullptr; } - std::string DebugString() const override { return "[]"; } bool IsEmpty() const override { return true; } @@ -79,22 +74,6 @@ class EmptyListValue final : public common_internal::CompatListValue { return CustomListValue(&EmptyListValue::Get(), arena); } - int size() const override { return 0; } - - CelValue operator[](int index) const override { - static const absl::NoDestructor error( - absl::InvalidArgumentError("index out of bounds")); - return CelValue::CreateError(&*error); - } - - CelValue Get(google::protobuf::Arena* arena, int index) const override { - if (arena == nullptr) { - return (*this)[index]; - } - return CelValue::CreateError(google::protobuf::Arena::Create( - arena, absl::InvalidArgumentError("index out of bounds"))); - } - private: absl::Status Get(size_t index, const google::protobuf::DescriptorPool* absl_nonnull, google::protobuf::MessageFactory* absl_nonnull, @@ -103,18 +82,14 @@ class EmptyListValue final : public common_internal::CompatListValue { *result = IndexOutOfBoundsError(index, arena); return absl::OkStatus(); } + + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } }; } // namespace -namespace common_internal { - -const CompatListValue* absl_nonnull EmptyCompatListValue() { - return &EmptyListValue::Get(); -} - -} // namespace common_internal - class CustomListValueInterfaceIterator final : public ValueIterator { public: explicit CustomListValueInterfaceIterator( diff --git a/common/values/custom_map_value.cc b/common/values/custom_map_value.cc index ce463f890..6e7a91c31 100644 --- a/common/values/custom_map_value.cc +++ b/common/values/custom_map_value.cc @@ -31,10 +31,7 @@ #include "common/native_type.h" #include "common/value.h" #include "common/value_kind.h" -#include "common/values/list_value_builder.h" -#include "common/values/map_value_builder.h" #include "common/values/values.h" -#include "eval/public/cel_value.h" #include "internal/status_macros.h" #include "internal/well_known_types.h" #include "google/protobuf/arena.h" @@ -48,8 +45,6 @@ namespace { using ::cel::well_known_types::StructReflection; using ::cel::well_known_types::ValueReflection; -using ::google::api::expr::runtime::CelList; -using ::google::api::expr::runtime::CelValue; absl::Status NoSuchKeyError(const Value& key) { return absl::NotFoundError( @@ -61,7 +56,7 @@ absl::Status InvalidMapKeyTypeError(ValueKind kind) { absl::StrCat("Invalid map key type: '", ValueKindToString(kind), "'")); } -class EmptyMapValue final : public common_internal::CompatMapValue { +class EmptyMapValue final : public CustomMapValueInterface { public: static const EmptyMapValue& Get() { static const absl::NoDestructor empty; @@ -70,8 +65,6 @@ class EmptyMapValue final : public common_internal::CompatMapValue { EmptyMapValue() = default; - google::protobuf::Arena* GetOwningArena() const override { return nullptr; } - std::string DebugString() const override { return "{}"; } bool IsEmpty() const override { return true; } @@ -109,28 +102,6 @@ class EmptyMapValue final : public common_internal::CompatMapValue { return CustomMapValue(); } - absl::optional operator[](CelValue key) const override { - return std::nullopt; - } - - using CompatMapValue::Get; - absl::optional Get(google::protobuf::Arena* arena, - CelValue key) const override { - return std::nullopt; - } - - absl::StatusOr Has(const CelValue& key) const override { return false; } - - int size() const override { return static_cast(Size()); } - - absl::StatusOr ListKeys() const override { - return common_internal::EmptyCompatListValue(); - } - - absl::StatusOr ListKeys(google::protobuf::Arena*) const override { - return ListKeys(); - } - private: absl::StatusOr Find( const Value& key, @@ -148,18 +119,14 @@ class EmptyMapValue final : public common_internal::CompatMapValue { google::protobuf::Arena* absl_nonnull arena) const override { return false; } + + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } }; } // namespace -namespace common_internal { - -const CompatMapValue* absl_nonnull EmptyCompatMapValue() { - return &EmptyMapValue::Get(); -} - -} // namespace common_internal - class CustomMapValueInterfaceIterator final : public ValueIterator { public: explicit CustomMapValueInterfaceIterator( diff --git a/common/values/legacy_list_value.cc b/common/values/legacy_list_value.cc index 50bf1e6fe..e9676437f 100644 --- a/common/values/legacy_list_value.cc +++ b/common/values/legacy_list_value.cc @@ -27,7 +27,6 @@ #include "common/native_type.h" #include "common/value.h" #include "common/values/legacy_struct_value.h" -#include "common/values/list_value_builder.h" #include "common/values/values.h" #include "eval/public/cel_value.h" #include "internal/casts.h" @@ -324,30 +323,10 @@ ListValue FromLegacyList( } return CustomListValue(); } - if (native_type_id == NativeTypeId::For()) { - const auto* impl = - cel::internal::down_cast(cel_list); - if (google::protobuf::Arena* arena = impl->GetOwningArena(); arena != nullptr) { - return CustomListValue(impl, arena); - } - } } return LegacyListValue(cel_list); } -absl::Status LegacyListValue::Equal( - const Value& other, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { - if (auto list_value = other.AsList(); list_value.has_value()) { - return ListValueEqual(*this, *list_value, descriptor_pool, message_factory, - arena, result); - } - *result = FalseValue(); - return absl::OkStatus(); -} - bool IsLegacyListValue(const Value& value) { return value.variant_.Is(); } @@ -362,17 +341,15 @@ absl::optional AsLegacyListValue(const Value& value) { return GetLegacyListValue(value); } if (auto custom_list_value = value.AsCustomList(); custom_list_value) { + if (custom_list_value->interface() == CustomListValue().interface()) { + return LegacyListValue(EmptyCompatListValue()); + } NativeTypeId native_type_id = custom_list_value->GetTypeId(); if (native_type_id == NativeTypeId::For()) { return LegacyListValue( static_cast( cel::internal::down_cast( custom_list_value->interface()))); - } else if (native_type_id == NativeTypeId::For()) { - return LegacyListValue( - static_cast( - cel::internal::down_cast( - custom_list_value->interface()))); } else if (native_type_id == NativeTypeId::For()) { return LegacyListValue(static_cast< diff --git a/common/values/legacy_list_value.h b/common/values/legacy_list_value.h index 55a169653..0ebe34f39 100644 --- a/common/values/legacy_list_value.h +++ b/common/values/legacy_list_value.h @@ -50,14 +50,76 @@ namespace common_internal { class LegacyListValue; +struct LegacyListValueDispatcher { + using DebugString = std::string (*)( + const google::api::expr::runtime::CelList* absl_nullability_unknown impl); + + using SerializeTo = absl::Status (*)( + const google::api::expr::runtime::CelList* absl_nullability_unknown impl, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output); + + using ConvertToJson = absl::Status (*)( + const google::api::expr::runtime::CelList* absl_nullability_unknown impl, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json); + + using ConvertToJsonArray = absl::Status (*)( + const google::api::expr::runtime::CelList* absl_nullability_unknown impl, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json); + + using IsEmpty = bool (*)( + const google::api::expr::runtime::CelList* absl_nullability_unknown impl); + + using Size = size_t (*)( + const google::api::expr::runtime::CelList* absl_nullability_unknown impl); + + using Get = absl::Status (*)( + const google::api::expr::runtime::CelList* absl_nullability_unknown impl, + size_t index, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result); + + using ForEach = absl::Status (*)( + const google::api::expr::runtime::CelList* absl_nullability_unknown impl, + CustomListValueInterface::ForEachWithIndexCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena); + + using NewIterator = absl::StatusOr (*)( + const google::api::expr::runtime::CelList* absl_nullability_unknown impl); + + using Contains = absl::Status (*)( + const google::api::expr::runtime::CelList* absl_nullability_unknown impl, + const Value& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result); + + absl_nonnull DebugString debug_string; + absl_nonnull SerializeTo serialize_to; + absl_nonnull ConvertToJson convert_to_json; + absl_nonnull ConvertToJsonArray convert_to_json_array; + absl_nonnull IsEmpty is_empty; + absl_nonnull Size size; + absl_nonnull Get get; + absl_nonnull ForEach for_each; + absl_nonnull NewIterator new_iterator; + absl_nonnull Contains contains; +}; + class LegacyListValue final : private common_internal::ListValueMixin { public: static constexpr ValueKind kKind = ValueKind::kList; explicit LegacyListValue( - const google::api::expr::runtime::CelList* absl_nullability_unknown impl) - : impl_(impl) {} + const google::api::expr::runtime::CelList* absl_nullability_unknown impl); // By default, this creates an empty list whose type is `list(dyn)`. Unless // you can help it, you should use a more specific typed list value. @@ -140,6 +202,7 @@ class LegacyListValue final friend void swap(LegacyListValue& lhs, LegacyListValue& rhs) noexcept { using std::swap; + swap(lhs.dispatcher_, rhs.dispatcher_); swap(lhs.impl_, rhs.impl_); } @@ -147,6 +210,7 @@ class LegacyListValue final friend class common_internal::ValueMixin; friend class common_internal::ListValueMixin; + const LegacyListValueDispatcher* absl_nullable dispatcher_ = nullptr; const google::api::expr::runtime::CelList* absl_nullability_unknown impl_ = nullptr; }; diff --git a/common/values/legacy_map_value.cc b/common/values/legacy_map_value.cc index bd025c51b..0b5bac765 100644 --- a/common/values/legacy_map_value.cc +++ b/common/values/legacy_map_value.cc @@ -24,6 +24,7 @@ #include "absl/base/nullability.h" #include "absl/log/absl_check.h" #include "absl/status/status.h" +#include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "common/legacy_value.h" @@ -33,7 +34,6 @@ #include "common/value_kind.h" #include "common/values/legacy_list_value.h" #include "common/values/legacy_struct_value.h" -#include "common/values/map_value_builder.h" #include "common/values/values.h" #include "eval/public/cel_value.h" #include "eval/public/structs/proto_message_type_adapter.h" @@ -88,6 +88,22 @@ absl::Status InvalidMapKeyType(absl::string_view key_type) { absl::StrCat("Invalid map key type: '", key_type, "'")); } +absl::StatusOr ToLegacyListKeys( + const ListValue& keys, google::protobuf::Arena* absl_nonnull arena) { + if (auto legacy_list = AsLegacyListValue(keys); legacy_list.has_value()) { + return legacy_list->cel_list(); + } + if (auto custom_list = keys.AsCustom(); custom_list.has_value()) { + CEL_ASSIGN_OR_RETURN( + const CompatListValue* compat_list, + MakeCompatListValue( + *custom_list, google::protobuf::DescriptorPool::generated_pool(), + google::protobuf::MessageFactory::generated_factory(), arena)); + return compat_list; + } + return absl::InternalError("failed to convert list keys to legacy list"); +} + } // namespace class LegacyParsedMapFieldMapValue final @@ -273,11 +289,7 @@ class LegacyParsedMapFieldMapValue final CEL_RETURN_IF_ERROR(value_.ListKeys( google::protobuf::DescriptorPool::generated_pool(), google::protobuf::MessageFactory::generated_factory(), arena, &keys)); - auto legacy_list = AsLegacyListValue(keys); - if (!legacy_list.has_value()) { - return absl::InternalError("failed to convert list keys to legacy list"); - } - return legacy_list->cel_list(); + return ToLegacyListKeys(keys, arena); } google::protobuf::Arena* absl_nonnull arena() const { return arena_; } @@ -461,11 +473,7 @@ class LegacyParsedJsonMapValue final CEL_RETURN_IF_ERROR(value_.ListKeys( google::protobuf::DescriptorPool::generated_pool(), google::protobuf::MessageFactory::generated_factory(), arena, &keys)); - auto legacy_list = AsLegacyListValue(keys); - if (!legacy_list.has_value()) { - return absl::InternalError("failed to convert list keys to legacy list"); - } - return legacy_list->cel_list(); + return ToLegacyListKeys(keys, arena); } google::protobuf::Arena* absl_nonnull arena() const { return arena_; } @@ -516,30 +524,10 @@ MapValue FromLegacyMap( } return CustomMapValue(); } - if (native_type_id == NativeTypeId::For()) { - const auto* impl = - cel::internal::down_cast(cel_map); - if (google::protobuf::Arena* arena = impl->GetOwningArena(); arena != nullptr) { - return CustomMapValue(impl, arena); - } - } } return LegacyMapValue(cel_map); } -absl::Status LegacyMapValue::Equal( - const Value& other, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { - if (auto map_value = other.AsMap(); map_value.has_value()) { - return MapValueEqual(*this, *map_value, descriptor_pool, message_factory, - arena, result); - } - *result = FalseValue(); - return absl::OkStatus(); -} - bool IsLegacyMapValue(const Value& value) { return value.variant_.Is(); } @@ -554,17 +542,15 @@ std::optional AsLegacyMapValue(const Value& value) { return GetLegacyMapValue(value); } if (auto custom_map_value = value.AsCustomMap(); custom_map_value) { + if (custom_map_value->interface() == CustomMapValue().interface()) { + return LegacyMapValue(EmptyCompatMapValue()); + } NativeTypeId native_type_id = NativeTypeId::Of(*custom_map_value); if (native_type_id == NativeTypeId::For()) { return LegacyMapValue( static_cast( cel::internal::down_cast( custom_map_value->interface()))); - } else if (native_type_id == NativeTypeId::For()) { - return LegacyMapValue( - static_cast( - cel::internal::down_cast( - custom_map_value->interface()))); } else if (native_type_id == NativeTypeId::For()) { return LegacyMapValue( diff --git a/common/values/legacy_map_value.h b/common/values/legacy_map_value.h index c170dea80..04849fdc1 100644 --- a/common/values/legacy_map_value.h +++ b/common/values/legacy_map_value.h @@ -49,14 +49,92 @@ namespace common_internal { class LegacyMapValue; +struct LegacyMapValueDispatcher { + using DebugString = std::string (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl); + + using SerializeTo = absl::Status (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output); + + using ConvertToJson = absl::Status (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json); + + using ConvertToJsonObject = absl::Status (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json); + + using IsEmpty = bool (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl); + + using Size = size_t (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl); + + using Get = absl::Status (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl, + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result); + + using Find = absl::StatusOr (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl, + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result); + + using Has = absl::Status (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl, + const Value& key, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result); + + using ListKeys = absl::Status (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, ListValue* absl_nonnull result); + + using ForEach = absl::Status (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl, + CustomMapValueInterface::ForEachCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena); + + using NewIterator = absl::StatusOr (*)( + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl); + + absl_nonnull DebugString debug_string; + absl_nonnull SerializeTo serialize_to; + absl_nonnull ConvertToJson convert_to_json; + absl_nonnull ConvertToJsonObject convert_to_json_object; + absl_nonnull IsEmpty is_empty; + absl_nonnull Size size; + absl_nonnull Get get; + absl_nonnull Find find; + absl_nonnull Has has; + absl_nonnull ListKeys list_keys; + absl_nonnull ForEach for_each; + absl_nonnull NewIterator new_iterator; +}; + class LegacyMapValue final : private common_internal::MapValueMixin { public: static constexpr ValueKind kKind = ValueKind::kMap; explicit LegacyMapValue( - const google::api::expr::runtime::CelMap* absl_nullability_unknown impl) - : impl_(impl) {} + const google::api::expr::runtime::CelMap* absl_nullability_unknown impl); // By default, this creates an empty map whose type is `map(dyn, dyn)`. // Unless you can help it, you should use a more specific typed map value. @@ -158,6 +236,7 @@ class LegacyMapValue final friend void swap(LegacyMapValue& lhs, LegacyMapValue& rhs) noexcept { using std::swap; + swap(lhs.dispatcher_, rhs.dispatcher_); swap(lhs.impl_, rhs.impl_); } @@ -165,6 +244,7 @@ class LegacyMapValue final friend class common_internal::ValueMixin; friend class common_internal::MapValueMixin; + const LegacyMapValueDispatcher* absl_nullable dispatcher_ = nullptr; const google::api::expr::runtime::CelMap* absl_nullability_unknown impl_ = nullptr; }; diff --git a/common/values/legacy_struct_value.cc b/common/values/legacy_struct_value.cc index aa342ad06..088a40ee0 100644 --- a/common/values/legacy_struct_value.cc +++ b/common/values/legacy_struct_value.cc @@ -12,18 +12,218 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#include + +#include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/log/absl_check.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "absl/types/span.h" +#include "base/attribute.h" #include "common/type.h" #include "common/value.h" +#include "runtime/runtime_options.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/io/zero_copy_stream.h" #include "google/protobuf/message.h" namespace cel::common_internal { StructType LegacyStructValue::GetRuntimeType() const { + if (ABSL_PREDICT_FALSE(message_ptr_ == nullptr)) { + ABSL_DCHECK(message_ptr_ != nullptr); + return StructType(); + } return MessageType(message_ptr_->GetDescriptor()); } +absl::string_view LegacyStructValue::GetTypeName() const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return ""; + } + return dispatcher_->get_type_name(message_ptr_, legacy_type_info_); +} + +std::string LegacyStructValue::DebugString() const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return ""; + } + return dispatcher_->debug_string(message_ptr_, legacy_type_info_); +} + +absl::Status LegacyStructValue::SerializeTo( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError( + "legacy struct value support is not linked"); + } + return dispatcher_->serialize_to(message_ptr_, legacy_type_info_, + descriptor_pool, message_factory, output); +} + +absl::Status LegacyStructValue::ConvertToJson( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError( + "legacy struct value support is not linked"); + } + return dispatcher_->convert_to_json(message_ptr_, legacy_type_info_, + descriptor_pool, message_factory, json); +} + +absl::Status LegacyStructValue::ConvertToJsonObject( + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError( + "legacy struct value support is not linked"); + } + return dispatcher_->convert_to_json_object( + message_ptr_, legacy_type_info_, descriptor_pool, message_factory, json); +} + +absl::Status LegacyStructValue::Equal( + const Value& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError( + "legacy struct value support is not linked"); + } + return dispatcher_->equal(message_ptr_, legacy_type_info_, other, + descriptor_pool, message_factory, arena, result); +} + +bool LegacyStructValue::IsZeroValue() const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return false; + } + return dispatcher_->is_zero_value(message_ptr_, legacy_type_info_); +} + +absl::Status LegacyStructValue::GetFieldByName( + absl::string_view name, ProtoWrapperTypeOptions unboxing_options, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError( + "legacy struct value support is not linked"); + } + return dispatcher_->get_field_by_name(message_ptr_, legacy_type_info_, name, + unboxing_options, descriptor_pool, + message_factory, arena, result); +} + +absl::Status LegacyStructValue::GetFieldByNumber( + int64_t number, ProtoWrapperTypeOptions unboxing_options, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError( + "legacy struct value support is not linked"); + } + return dispatcher_->get_field_by_number( + message_ptr_, legacy_type_info_, number, unboxing_options, + descriptor_pool, message_factory, arena, result); +} + +absl::StatusOr LegacyStructValue::HasFieldByName( + absl::string_view name) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError( + "legacy struct value support is not linked"); + } + return dispatcher_->has_field_by_name(message_ptr_, legacy_type_info_, name); +} + +absl::StatusOr LegacyStructValue::HasFieldByNumber(int64_t number) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError( + "legacy struct value support is not linked"); + } + return dispatcher_->has_field_by_number(message_ptr_, legacy_type_info_, + number); +} + +absl::Status LegacyStructValue::ForEachField( + ForEachFieldCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError( + "legacy struct value support is not linked"); + } + return dispatcher_->for_each_field(message_ptr_, legacy_type_info_, callback, + descriptor_pool, message_factory, arena); +} + +absl::Status LegacyStructValue::Qualify( + absl::Span qualifiers, bool presence_test, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result, + int* absl_nonnull count) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError( + "legacy struct value support is not linked"); + } + return dispatcher_->qualify(message_ptr_, legacy_type_info_, qualifiers, + presence_test, descriptor_pool, message_factory, + arena, result, count); +} + +const google::protobuf::Message* absl_nullable LegacyStructValue::GetLegacyMessage() + const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return nullptr; + } + return dispatcher_->get_legacy_message(message_ptr_, legacy_type_info_); +} + +absl::Status LegacyStructValue::WrapLegacyMessageField( + const google::protobuf::FieldDescriptor* absl_nonnull field_descriptor, + ProtoWrapperTypeOptions unboxing_option, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull out) const { + if (ABSL_PREDICT_FALSE(dispatcher_ == nullptr)) { + ABSL_DCHECK(dispatcher_ != nullptr); + return absl::UnimplementedError( + "legacy struct value support is not linked"); + } + return dispatcher_->wrap_legacy_message_field( + message_ptr_, field_descriptor, unboxing_option, descriptor_pool, + message_factory, arena, out); +} + bool IsLegacyStructValue(const Value& value) { return value.variant_.Is(); } @@ -40,4 +240,11 @@ absl::optional AsLegacyStructValue(const Value& value) { return std::nullopt; } +const google::protobuf::Message* absl_nullable GetLegacyMessage(const Value& value) { + if (auto legacy = AsLegacyStructValue(value); legacy.has_value()) { + return legacy->GetLegacyMessage(); + } + return nullptr; +} + } // namespace cel::common_internal diff --git a/common/values/legacy_struct_value.h b/common/values/legacy_struct_value.h index ab5baed1e..59b5a877f 100644 --- a/common/values/legacy_struct_value.h +++ b/common/values/legacy_struct_value.h @@ -53,6 +53,134 @@ namespace common_internal { class LegacyStructValue; +struct LegacyStructValueDispatcher { + using GetTypeName = absl::string_view (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info); + + using DebugString = std::string (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info); + + using SerializeTo = absl::Status (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::io::ZeroCopyOutputStream* absl_nonnull output); + + using ConvertToJson = absl::Status (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json); + + using ConvertToJsonObject = absl::Status (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Message* absl_nonnull json); + + using Equal = absl::Status (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + const Value& other, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result); + + using IsZeroValue = bool (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info); + + using GetFieldByName = absl::Status (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + absl::string_view name, ProtoWrapperTypeOptions unboxing_options, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result); + + using GetFieldByNumber = absl::Status (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + int64_t number, ProtoWrapperTypeOptions unboxing_options, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result); + + using HasFieldByName = absl::StatusOr (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + absl::string_view name); + + using HasFieldByNumber = absl::StatusOr (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + int64_t number); + + using ForEachField = absl::Status (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + CustomStructValueInterface::ForEachFieldCallback callback, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena); + + using Qualify = absl::Status (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info, + absl::Span qualifiers, bool presence_test, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull result, + int* absl_nonnull count); + + using GetLegacyMessage = const google::protobuf::Message* absl_nullable (*)( + const google::protobuf::Message* absl_nullability_unknown message_ptr, + const google::api::expr::runtime:: + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info); + + using WrapLegacyMessageField = absl::Status (*)( + const google::protobuf::Message* absl_nonnull message, + const google::protobuf::FieldDescriptor* absl_nonnull field_descriptor, + ProtoWrapperTypeOptions unboxing_option, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull out); + + absl_nonnull GetTypeName get_type_name; + absl_nonnull DebugString debug_string; + absl_nonnull SerializeTo serialize_to; + absl_nonnull ConvertToJson convert_to_json; + absl_nonnull ConvertToJsonObject convert_to_json_object; + absl_nonnull Equal equal; + absl_nonnull IsZeroValue is_zero_value; + absl_nonnull GetFieldByName get_field_by_name; + absl_nonnull GetFieldByNumber get_field_by_number; + absl_nonnull HasFieldByName has_field_by_name; + absl_nonnull HasFieldByNumber has_field_by_number; + absl_nonnull ForEachField for_each_field; + absl_nonnull Qualify qualify; + absl_nonnull GetLegacyMessage get_legacy_message; + absl_nonnull WrapLegacyMessageField wrap_legacy_message_field; +}; + // `LegacyStructValue` is a wrapper around the old representation of protocol // buffer messages in `google::api::expr::runtime::CelValue`. It only supports // arena allocation. @@ -66,11 +194,12 @@ class LegacyStructValue final LegacyStructValue( const google::protobuf::Message* absl_nullability_unknown message_ptr, const google::api::expr::runtime:: - LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info) - : message_ptr_(message_ptr), legacy_type_info_(legacy_type_info) {} + LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info); LegacyStructValue(const LegacyStructValue&) = default; + LegacyStructValue(LegacyStructValue&&) = default; LegacyStructValue& operator=(const LegacyStructValue&) = default; + LegacyStructValue& operator=(LegacyStructValue&&) = default; constexpr ValueKind kind() const { return kKind; } @@ -141,6 +270,15 @@ class LegacyStructValue final int* absl_nonnull count) const; using StructValueMixin::Qualify; + const google::protobuf::Message* absl_nullable GetLegacyMessage() const; + + absl::Status WrapLegacyMessageField( + const google::protobuf::FieldDescriptor* absl_nonnull field_descriptor, + ProtoWrapperTypeOptions unboxing_option, + const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, + google::protobuf::MessageFactory* absl_nonnull message_factory, + google::protobuf::Arena* absl_nonnull arena, Value* absl_nonnull out) const; + const google::protobuf::Message* absl_nullability_unknown message_ptr() const { return message_ptr_; } @@ -152,6 +290,7 @@ class LegacyStructValue final friend void swap(LegacyStructValue& lhs, LegacyStructValue& rhs) noexcept { using std::swap; + swap(lhs.dispatcher_, rhs.dispatcher_); swap(lhs.message_ptr_, rhs.message_ptr_); swap(lhs.legacy_type_info_, rhs.legacy_type_info_); } @@ -160,6 +299,7 @@ class LegacyStructValue final friend class common_internal::ValueMixin; friend class common_internal::StructValueMixin; + const LegacyStructValueDispatcher* absl_nullable dispatcher_ = nullptr; const google::protobuf::Message* absl_nullability_unknown message_ptr_ = nullptr; const google::api::expr::runtime::LegacyTypeInfoApis* absl_nullability_unknown legacy_type_info_ = nullptr; @@ -176,6 +316,8 @@ LegacyStructValue GetLegacyStructValue(const Value& value); absl::optional AsLegacyStructValue(const Value& value); +const google::protobuf::Message* absl_nullable GetLegacyMessage(const Value& value); + } // namespace common_internal } // namespace cel diff --git a/common/values/list_value_builder.h b/common/values/list_value_builder.h index d7af362c9..8e3b1c2b2 100644 --- a/common/values/list_value_builder.h +++ b/common/values/list_value_builder.h @@ -20,13 +20,9 @@ #include "absl/base/attributes.h" #include "absl/base/nullability.h" #include "absl/status/status.h" -#include "absl/status/statusor.h" #include "common/native_type.h" #include "common/value.h" -#include "eval/public/cel_value.h" #include "google/protobuf/arena.h" -#include "google/protobuf/descriptor.h" -#include "google/protobuf/message.h" namespace cel { @@ -34,28 +30,6 @@ class ValueFactory; namespace common_internal { -// Special implementation of list which is both a modern list and legacy list. -// Do not try this at home. This should only be implemented in -// `list_value_builder.cc`. -class CompatListValue : public CustomListValueInterface, - public google::api::expr::runtime::CelList { - public: - virtual google::protobuf::Arena* GetOwningArena() const = 0; - - private: - NativeTypeId GetNativeTypeId() const final { - return NativeTypeId::For(); - } -}; - -const CompatListValue* absl_nonnull EmptyCompatListValue(); - -absl::StatusOr MakeCompatListValue( - const CustomListValue& value, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena); - // Extension of ParsedListValueInterface which is also mutable. Accessing this // like a normal list before all elements are finished being appended is a bug. // This is primarily used by the runtime to efficiently implement comprehensions @@ -74,22 +48,6 @@ class MutableListValue : public CustomListValueInterface { } }; -// Special implementation of list which is both a modern list, legacy list, and -// mutable. -// -// NOTE: We do not extend CompatListValue to avoid having to use virtual -// inheritance and `dynamic_cast`. -class MutableCompatListValue : public MutableListValue, - public google::api::expr::runtime::CelList { - public: - virtual google::protobuf::Arena* GetOwningArena() const = 0; - - private: - NativeTypeId GetNativeTypeId() const final { - return NativeTypeId::For(); - } -}; - MutableListValue* absl_nonnull NewMutableListValue( google::protobuf::Arena* absl_nonnull arena ABSL_ATTRIBUTE_LIFETIME_BOUND); diff --git a/common/values/map_value_builder.h b/common/values/map_value_builder.h index 2eb70b613..4e67b56f0 100644 --- a/common/values/map_value_builder.h +++ b/common/values/map_value_builder.h @@ -20,13 +20,9 @@ #include "absl/base/attributes.h" #include "absl/base/nullability.h" #include "absl/status/status.h" -#include "absl/status/statusor.h" #include "common/native_type.h" #include "common/value.h" -#include "eval/public/cel_value.h" #include "google/protobuf/arena.h" -#include "google/protobuf/descriptor.h" -#include "google/protobuf/message.h" namespace cel { @@ -34,28 +30,6 @@ class ValueFactory; namespace common_internal { -// Special implementation of map which is both a modern map and legacy map. Do -// not try this at home. This should only be implemented in -// `map_value_builder.cc`. -class CompatMapValue : public CustomMapValueInterface, - public google::api::expr::runtime::CelMap { - public: - virtual google::protobuf::Arena* GetOwningArena() const = 0; - - private: - NativeTypeId GetNativeTypeId() const final { - return NativeTypeId::For(); - } -}; - -const CompatMapValue* absl_nonnull EmptyCompatMapValue(); - -absl::StatusOr MakeCompatMapValue( - const CustomMapValue& value, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena); - // Extension of ParsedMapValueInterface which is also mutable. Accessing this // like a normal map before all entries are finished being inserted is a bug. // This is primarily used by the runtime to efficiently implement comprehensions @@ -74,22 +48,6 @@ class MutableMapValue : public CustomMapValueInterface { } }; -// Special implementation of map which is both a modern map, legacy map, and -// mutable. -// -// NOTE: We do not extend CompatMapValue to avoid having to use virtual -// inheritance and `dynamic_cast`. -class MutableCompatMapValue : public MutableMapValue, - public google::api::expr::runtime::CelMap { - public: - virtual google::protobuf::Arena* GetOwningArena() const = 0; - - private: - NativeTypeId GetNativeTypeId() const final { - return NativeTypeId::For(); - } -}; - MutableMapValue* absl_nonnull NewMutableMapValue( google::protobuf::Arena* absl_nonnull arena); diff --git a/common/values/parsed_message_value.h b/common/values/parsed_message_value.h index 1d02a4369..f736ab69a 100644 --- a/common/values/parsed_message_value.h +++ b/common/values/parsed_message_value.h @@ -106,6 +106,9 @@ class ParsedMessageValue final const google::protobuf::Message* absl_nonnull operator->() const { return value_; } const google::protobuf::Message* absl_nonnull message() const { return value_; } + // Internal: returns true if this value was created without an arena. + bool is_unsafe() const { return arena_ == nullptr; } + bool IsZeroValue() const; std::string DebugString() const; @@ -216,8 +219,6 @@ class ParsedMessageValue final return absl::OkStatus(); } - bool is_unsafe() const { return arena_ == nullptr; } - const google::protobuf::Message* absl_nonnull value_; // The arena attributed as Owning this value. Null if the value is created by diff --git a/common/values/value_builder.cc b/common/values/value_builder.cc index 526bd4adc..f490fb9c7 100644 --- a/common/values/value_builder.cc +++ b/common/values/value_builder.cc @@ -36,14 +36,12 @@ #include "absl/types/optional.h" #include "absl/types/span.h" #include "common/allocator.h" -#include "common/legacy_value.h" #include "common/native_type.h" #include "common/type.h" #include "common/value.h" #include "common/value_kind.h" #include "common/values/list_value_builder.h" #include "common/values/map_value_builder.h" -#include "eval/public/cel_value.h" #include "internal/casts.h" #include "internal/manual.h" #include "internal/status_macros.h" @@ -61,7 +59,6 @@ namespace { using ::cel::well_known_types::ListValueReflection; using ::cel::well_known_types::StructReflection; using ::cel::well_known_types::ValueReflection; -using ::google::api::expr::runtime::CelValue; using ValueVector = std::vector>; @@ -122,9 +119,9 @@ absl::Status ListValueToJson( reflection.MutableListValue(json)); } -class CompatListValueImplIterator final : public ValueIterator { +class ListValueImplIterator final : public ValueIterator { public: - explicit CompatListValueImplIterator(absl::Span elements) + explicit ListValueImplIterator(absl::Span elements) : elements_(elements) {} bool HasNext() override { return index_ < elements_.size(); } @@ -198,6 +195,8 @@ struct ValueFormatter { } }; +class ListValueImpl; + class ListValueBuilderImpl final : public ListValueBuilder { public: explicit ListValueBuilderImpl(google::protobuf::Arena* absl_nonnull arena) @@ -224,25 +223,20 @@ class ListValueBuilderImpl final : public ListValueBuilder { CustomListValue BuildCustom() &&; - const CompatListValue* absl_nonnull BuildCompat() &&; + const ListValueImpl* absl_nonnull BuildImpl() &&; - const CompatListValue* absl_nonnull BuildCompatAt( - void* absl_nonnull address) &&; + const ListValueImpl* absl_nonnull BuildAt(void* absl_nonnull address) &&; private: google::protobuf::Arena* absl_nonnull const arena_; internal::Manual elements_; }; -class CompatListValueImpl final : public CompatListValue { +class ListValueImpl final : public CustomListValueInterface { public: - explicit CompatListValueImpl(ValueVector&& elements) + explicit ListValueImpl(ValueVector&& elements) : elements_(std::move(elements)) {} - google::protobuf::Arena* GetOwningArena() const override { - return elements_.get_allocator().arena(); - } - std::string DebugString() const override { return absl::StrCat("[", absl::StrJoin(elements_, ", ", ValueFormatter{}), "]"); @@ -285,32 +279,10 @@ class CompatListValueImpl final : public CompatListValue { } absl::StatusOr NewIterator() const override { - return std::make_unique( + return std::make_unique( absl::MakeConstSpan(elements_)); } - CelValue operator[](int index) const override { - return Get(elements_.get_allocator().arena(), index); - } - - // Like `operator[](int)` above, but also accepts an arena. Prefer calling - // this variant if the arena is known. - CelValue Get(google::protobuf::Arena* arena, int index) const override { - if (arena == nullptr) { - arena = elements_.get_allocator().arena(); - } - if (ABSL_PREDICT_FALSE(index < 0 || index >= size())) { - return CelValue::CreateError(google::protobuf::Arena::Create( - arena, common_internal::MakeIndexOutOfBoundsError(index))); - } - return common_internal::UnsafeLegacyValue( - elements_[index], - /*stable=*/true, - arena != nullptr ? arena : elements_.get_allocator().arena()); - } - - int size() const override { return static_cast(Size()); } - protected: absl::Status Get(size_t index, const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, @@ -326,6 +298,10 @@ class CompatListValueImpl final : public CompatListValue { } private: + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } + const ValueVector elements_; }; @@ -338,35 +314,28 @@ ListValue ListValueBuilderImpl::Build() && { CustomListValue ListValueBuilderImpl::BuildCustom() && { if (elements_->empty()) { - return CustomListValue(EmptyCompatListValue(), arena_); + return CustomListValue(); } - return CustomListValue(std::move(*this).BuildCompat(), arena_); + return CustomListValue(std::move(*this).BuildImpl(), arena_); } -const CompatListValue* absl_nonnull ListValueBuilderImpl::BuildCompat() && { - if (elements_->empty()) { - return EmptyCompatListValue(); - } - return std::move(*this).BuildCompatAt(arena_->AllocateAligned( - sizeof(CompatListValueImpl), alignof(CompatListValueImpl))); +const ListValueImpl* absl_nonnull ListValueBuilderImpl::BuildImpl() && { + return std::move(*this).BuildAt( + arena_->AllocateAligned(sizeof(ListValueImpl), alignof(ListValueImpl))); } -const CompatListValue* absl_nonnull ListValueBuilderImpl::BuildCompatAt( +const ListValueImpl* absl_nonnull ListValueBuilderImpl::BuildAt( void* absl_nonnull address) && { - CompatListValueImpl* absl_nonnull impl = - ::new (address) CompatListValueImpl(std::move(*elements_)); + ListValueImpl* absl_nonnull impl = + ::new (address) ListValueImpl(std::move(*elements_)); return impl; } -class MutableCompatListValueImpl final : public MutableCompatListValue { +class MutableListValueImpl final : public MutableListValue { public: - explicit MutableCompatListValueImpl(google::protobuf::Arena* absl_nonnull arena) + explicit MutableListValueImpl(google::protobuf::Arena* absl_nonnull arena) : elements_(arena) {} - google::protobuf::Arena* GetOwningArena() const override { - return elements_.get_allocator().arena(); - } - std::string DebugString() const override { return absl::StrCat("[", absl::StrJoin(elements_, ", ", ValueFormatter{}), "]"); @@ -409,31 +378,10 @@ class MutableCompatListValueImpl final : public MutableCompatListValue { } absl::StatusOr NewIterator() const override { - return std::make_unique( + return std::make_unique( absl::MakeConstSpan(elements_)); } - CelValue operator[](int index) const override { - return Get(elements_.get_allocator().arena(), index); - } - - // Like `operator[](int)` above, but also accepts an arena. Prefer calling - // this variant if the arena is known. - CelValue Get(google::protobuf::Arena* arena, int index) const override { - if (arena == nullptr) { - arena = elements_.get_allocator().arena(); - } - if (ABSL_PREDICT_FALSE(index < 0 || index >= size())) { - return CelValue::CreateError(google::protobuf::Arena::Create( - arena, common_internal::MakeIndexOutOfBoundsError(index))); - } - return common_internal::UnsafeLegacyValue( - elements_[index], /*stable=*/false, - arena != nullptr ? arena : elements_.get_allocator().arena()); - } - - int size() const override { return static_cast(Size()); } - absl::Status Append(Value value) const override { CEL_RETURN_IF_ERROR(CheckListElement(value)); elements_.emplace_back(std::move(value)); @@ -462,64 +410,36 @@ class MutableCompatListValueImpl final : public MutableCompatListValue { } // namespace -absl::StatusOr MakeCompatListValue( - const CustomListValue& value, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) { - ListValueBuilderImpl builder(arena); - builder.Reserve(value.Size()); - - CEL_RETURN_IF_ERROR(value.ForEach( - [&](const Value& element) -> absl::StatusOr { - CEL_RETURN_IF_ERROR(builder.Add(element)); - return true; - }, - descriptor_pool, message_factory, arena)); - - return std::move(builder).BuildCompat(); -} - MutableListValue* absl_nonnull NewMutableListValue( google::protobuf::Arena* absl_nonnull arena) { - return ::new (arena->AllocateAligned(sizeof(MutableCompatListValueImpl), - alignof(MutableCompatListValueImpl))) - MutableCompatListValueImpl(arena); + return ::new (arena->AllocateAligned(sizeof(MutableListValueImpl), + alignof(MutableListValueImpl))) + MutableListValueImpl(arena); } bool IsMutableListValue(const Value& value) { if (auto custom_list_value = value.AsCustomList(); custom_list_value) { - NativeTypeId native_type_id = custom_list_value->GetTypeId(); - if (native_type_id == NativeTypeId::For() || - native_type_id == NativeTypeId::For()) { - return true; - } + return custom_list_value->GetTypeId() == + NativeTypeId::For(); } return false; } bool IsMutableListValue(const ListValue& value) { if (auto custom_list_value = value.AsCustom(); custom_list_value) { - NativeTypeId native_type_id = custom_list_value->GetTypeId(); - if (native_type_id == NativeTypeId::For() || - native_type_id == NativeTypeId::For()) { - return true; - } + return custom_list_value->GetTypeId() == + NativeTypeId::For(); } return false; } const MutableListValue* absl_nullable AsMutableListValue(const Value& value) { if (auto custom_list_value = value.AsCustomList(); custom_list_value) { - NativeTypeId native_type_id = custom_list_value->GetTypeId(); - if (native_type_id == NativeTypeId::For()) { + if (custom_list_value->GetTypeId() == + NativeTypeId::For()) { return cel::internal::down_cast( custom_list_value->interface()); } - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - custom_list_value->interface()); - } } return nullptr; } @@ -527,47 +447,25 @@ const MutableListValue* absl_nullable AsMutableListValue(const Value& value) { const MutableListValue* absl_nullable AsMutableListValue( const ListValue& value) { if (auto custom_list_value = value.AsCustom(); custom_list_value) { - NativeTypeId native_type_id = custom_list_value->GetTypeId(); - if (native_type_id == NativeTypeId::For()) { + if (custom_list_value->GetTypeId() == + NativeTypeId::For()) { return cel::internal::down_cast( custom_list_value->interface()); } - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - custom_list_value->interface()); - } } return nullptr; } const MutableListValue& GetMutableListValue(const Value& value) { ABSL_DCHECK(IsMutableListValue(value)) << value; - const auto& custom_list_value = value.GetCustomList(); - NativeTypeId native_type_id = custom_list_value.GetTypeId(); - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - *custom_list_value.interface()); - } - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - *custom_list_value.interface()); - } - ABSL_UNREACHABLE(); + return cel::internal::down_cast( + *value.GetCustomList().interface()); } const MutableListValue& GetMutableListValue(const ListValue& value) { ABSL_DCHECK(IsMutableListValue(value)) << value; - const auto& custom_list_value = value.GetCustom(); - NativeTypeId native_type_id = custom_list_value.GetTypeId(); - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - *custom_list_value.interface()); - } - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - *custom_list_value.interface()); - } - ABSL_UNREACHABLE(); + return cel::internal::down_cast( + *value.GetCustom().interface()); } absl_nonnull cel::ListValueBuilderPtr NewListValueBuilder( @@ -585,9 +483,6 @@ namespace common_internal { namespace { -using ::google::api::expr::runtime::CelList; -using ::google::api::expr::runtime::CelValue; - absl::Status CheckMapValue(const Value& value) { if (auto error_value = value.AsError(); ABSL_PREDICT_FALSE(error_value)) { return error_value->ToStatus(); @@ -616,21 +511,6 @@ size_t ValueHash(const Value& value) { } } -size_t ValueHash(const CelValue& value) { - switch (value.type()) { - case CelValue::Type::kBool: - return absl::HashOf(ValueKind::kBool, value.BoolOrDie()); - case CelValue::Type::kInt: - return absl::HashOf(ValueKind::kInt, value.Int64OrDie()); - case CelValue::Type::kUint: - return absl::HashOf(ValueKind::kUint, value.Uint64OrDie()); - case CelValue::Type::kString: - return absl::HashOf(ValueKind::kString, value.StringOrDie().value()); - default: - ABSL_UNREACHABLE(); - } -} - bool ValueEquals(const Value& lhs, const Value& rhs) { switch (lhs.kind()) { case ValueKind::kBool: @@ -690,65 +570,6 @@ bool ValueEquals(const Value& lhs, const Value& rhs) { } } -bool CelValueEquals(const CelValue& lhs, const Value& rhs) { - switch (lhs.type()) { - case CelValue::Type::kBool: - switch (rhs.kind()) { - case ValueKind::kBool: - return BoolValue(lhs.BoolOrDie()) == rhs.GetBool(); - case ValueKind::kInt: - return false; - case ValueKind::kUint: - return false; - case ValueKind::kString: - return false; - default: - ABSL_UNREACHABLE(); - } - case CelValue::Type::kInt: - switch (rhs.kind()) { - case ValueKind::kBool: - return false; - case ValueKind::kInt: - return IntValue(lhs.Int64OrDie()) == rhs.GetInt(); - case ValueKind::kUint: - return false; - case ValueKind::kString: - return false; - default: - ABSL_UNREACHABLE(); - } - case CelValue::Type::kUint: - switch (rhs.kind()) { - case ValueKind::kBool: - return false; - case ValueKind::kInt: - return false; - case ValueKind::kUint: - return UintValue(lhs.Uint64OrDie()) == rhs.GetUint(); - case ValueKind::kString: - return false; - default: - ABSL_UNREACHABLE(); - } - case CelValue::Type::kString: - switch (rhs.kind()) { - case ValueKind::kBool: - return false; - case ValueKind::kInt: - return false; - case ValueKind::kUint: - return false; - case ValueKind::kString: - return rhs.GetString().Equals(lhs.StringOrDie().value()); - default: - ABSL_UNREACHABLE(); - } - default: - ABSL_UNREACHABLE(); - } -} - absl::StatusOr ValueToJsonString(const Value& value) { switch (value.kind()) { case ValueKind::kString: @@ -805,24 +626,10 @@ absl::Status MapValueToJson( } struct ValueHasher { - using is_transparent = void; - size_t operator()(const Value& value) const { return (ValueHash)(value); } - - size_t operator()(const CelValue& value) const { return (ValueHash)(value); } }; struct ValueEqualer { - using is_transparent = void; - - bool operator()(const Value& lhs, const CelValue& rhs) const { - return (*this)(rhs, lhs); - } - - bool operator()(const CelValue& lhs, const Value& rhs) const { - return (CelValueEquals)(lhs, rhs); - } - bool operator()(const Value& lhs, const Value& rhs) const { return (ValueEquals)(lhs, rhs); } @@ -834,9 +641,9 @@ using ValueFlatHashMap = absl::flat_hash_map; -class CompatMapValueImplIterator final : public ValueIterator { +class MapValueImplIterator final : public ValueIterator { public: - explicit CompatMapValueImplIterator(const ValueFlatHashMap* absl_nonnull map) + explicit MapValueImplIterator(const ValueFlatHashMap* absl_nonnull map) : begin_(map->begin()), end_(map->end()) {} bool HasNext() override { return begin_ != end_; } @@ -899,6 +706,8 @@ class CompatMapValueImplIterator final : public ValueIterator { const typename ValueFlatHashMap::const_iterator end_; }; +class MapValueImpl; + class MapValueBuilderImpl final : public MapValueBuilder { public: explicit MapValueBuilderImpl(google::protobuf::Arena* absl_nonnull arena) @@ -929,20 +738,16 @@ class MapValueBuilderImpl final : public MapValueBuilder { CustomMapValue BuildCustom() &&; - const CompatMapValue* absl_nonnull BuildCompat() &&; + const MapValueImpl* absl_nonnull BuildImpl() &&; private: google::protobuf::Arena* absl_nonnull const arena_; internal::Manual map_; }; -class CompatMapValueImpl final : public CompatMapValue { +class MapValueImpl final : public CustomMapValueInterface { public: - explicit CompatMapValueImpl(ValueFlatHashMap&& map) : map_(std::move(map)) {} - - google::protobuf::Arena* GetOwningArena() const override { - return map_.get_allocator().arena(); - } + explicit MapValueImpl(ValueFlatHashMap&& map) : map_(std::move(map)) {} std::string DebugString() const override { return absl::StrCat("{", absl::StrJoin(map_, ", ", ValueFormatter{}), "}"); @@ -992,42 +797,7 @@ class CompatMapValueImpl final : public CompatMapValue { } absl::StatusOr NewIterator() const override { - return std::make_unique(&map_); - } - - absl::optional operator[](CelValue key) const override { - return Get(map_.get_allocator().arena(), key); - } - - using CompatMapValue::Get; - absl::optional Get(google::protobuf::Arena* arena, - CelValue key) const override { - if (auto status = CelValue::CheckMapKeyType(key); !status.ok()) { - status.IgnoreError(); - return std::nullopt; - } - if (auto it = map_.find(key); it != map_.end()) { - return common_internal::UnsafeLegacyValue( - it->second, /*stable=*/true, - arena != nullptr ? arena : map_.get_allocator().arena()); - } - return std::nullopt; - } - - absl::StatusOr Has(const CelValue& key) const override { - // This check safeguards against issues with invalid key types such as NaN. - CEL_RETURN_IF_ERROR(CelValue::CheckMapKeyType(key)); - return map_.find(key) != map_.end(); - } - - int size() const override { return static_cast(Size()); } - - absl::StatusOr ListKeys() const override { - return ProjectKeys(); - } - - absl::StatusOr ListKeys(google::protobuf::Arena* arena) const override { - return ProjectKeys(); + return std::make_unique(&map_); } protected: @@ -1055,7 +825,11 @@ class CompatMapValueImpl final : public CompatMapValue { } private: - const CompatListValue* absl_nonnull ProjectKeys() const { + NativeTypeId GetNativeTypeId() const override { + return NativeTypeId::For(); + } + + const CustomListValueInterface* absl_nonnull ProjectKeys() const { absl::call_once(keys_once_, [this]() { ListValueBuilderImpl builder(map_.get_allocator().arena()); builder.Reserve(map_.size()); @@ -1064,15 +838,14 @@ class CompatMapValueImpl final : public CompatMapValue { builder.UnsafeAdd(entry.first); } - std::move(builder).BuildCompatAt(&keys_[0]); + std::move(builder).BuildAt(&keys_[0]); }); - return std::launder( - reinterpret_cast(&keys_[0])); + return std::launder(reinterpret_cast(&keys_[0])); } const ValueFlatHashMap map_; mutable absl::once_flag keys_once_; - alignas(CompatListValueImpl) mutable char keys_[sizeof(CompatListValueImpl)]; + alignas(ListValueImpl) mutable char keys_[sizeof(ListValueImpl)]; }; MapValue MapValueBuilderImpl::Build() && { @@ -1084,30 +857,23 @@ MapValue MapValueBuilderImpl::Build() && { CustomMapValue MapValueBuilderImpl::BuildCustom() && { if (map_->empty()) { - return CustomMapValue(EmptyCompatMapValue(), arena_); + return CustomMapValue(); } - return CustomMapValue(std::move(*this).BuildCompat(), arena_); + return CustomMapValue(std::move(*this).BuildImpl(), arena_); } -const CompatMapValue* absl_nonnull MapValueBuilderImpl::BuildCompat() && { - if (map_->empty()) { - return EmptyCompatMapValue(); - } - CompatMapValueImpl* absl_nonnull impl = ::new (arena_->AllocateAligned( - sizeof(CompatMapValueImpl), alignof(CompatMapValueImpl))) - CompatMapValueImpl(std::move(*map_)); +const MapValueImpl* absl_nonnull MapValueBuilderImpl::BuildImpl() && { + MapValueImpl* absl_nonnull impl = ::new ( + arena_->AllocateAligned(sizeof(MapValueImpl), alignof(MapValueImpl))) + MapValueImpl(std::move(*map_)); return impl; } -class MutableCompatMapValueImpl final : public MutableCompatMapValue { +class MutableMapValueImpl final : public MutableMapValue { public: - explicit MutableCompatMapValueImpl(google::protobuf::Arena* absl_nonnull arena) + explicit MutableMapValueImpl(google::protobuf::Arena* absl_nonnull arena) : map_(arena) {} - google::protobuf::Arena* GetOwningArena() const override { - return map_.get_allocator().arena(); - } - std::string DebugString() const override { return absl::StrCat("{", absl::StrJoin(map_, ", ", ValueFormatter{}), "}"); } @@ -1156,42 +922,7 @@ class MutableCompatMapValueImpl final : public MutableCompatMapValue { } absl::StatusOr NewIterator() const override { - return std::make_unique(&map_); - } - - absl::optional operator[](CelValue key) const override { - return Get(map_.get_allocator().arena(), key); - } - - using MutableCompatMapValue::Get; - absl::optional Get(google::protobuf::Arena* arena, - CelValue key) const override { - if (auto status = CelValue::CheckMapKeyType(key); !status.ok()) { - status.IgnoreError(); - return std::nullopt; - } - if (auto it = map_.find(key); it != map_.end()) { - return common_internal::UnsafeLegacyValue( - it->second, /*stable=*/false, - arena != nullptr ? arena : map_.get_allocator().arena()); - } - return std::nullopt; - } - - absl::StatusOr Has(const CelValue& key) const override { - // This check safeguards against issues with invalid key types such as NaN. - CEL_RETURN_IF_ERROR(CelValue::CheckMapKeyType(key)); - return map_.find(key) != map_.end(); - } - - int size() const override { return static_cast(Size()); } - - absl::StatusOr ListKeys() const override { - return ProjectKeys(); - } - - absl::StatusOr ListKeys(google::protobuf::Arena* arena) const override { - return ProjectKeys(); + return std::make_unique(&map_); } absl::Status Put(Value key, Value value) const override { @@ -1232,7 +963,7 @@ class MutableCompatMapValueImpl final : public MutableCompatMapValue { } private: - const CompatListValue* absl_nonnull ProjectKeys() const { + const CustomListValueInterface* absl_nonnull ProjectKeys() const { absl::call_once(keys_once_, [this]() { ListValueBuilderImpl builder(map_.get_allocator().arena()); builder.Reserve(map_.size()); @@ -1241,124 +972,71 @@ class MutableCompatMapValueImpl final : public MutableCompatMapValue { builder.UnsafeAdd(entry.first); } - std::move(builder).BuildCompatAt(&keys_[0]); + std::move(builder).BuildAt(&keys_[0]); }); - return std::launder( - reinterpret_cast(&keys_[0])); + return std::launder(reinterpret_cast(&keys_[0])); } mutable ValueFlatHashMap map_; mutable absl::once_flag keys_once_; - alignas(CompatListValueImpl) mutable char keys_[sizeof(CompatListValueImpl)]; + alignas(ListValueImpl) mutable char keys_[sizeof(ListValueImpl)]; }; } // namespace -absl::StatusOr MakeCompatMapValue( - const CustomMapValue& value, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) { - MapValueBuilderImpl builder(arena); - builder.Reserve(value.Size()); - - CEL_RETURN_IF_ERROR(value.ForEach( - [&](const Value& key, const Value& value) -> absl::StatusOr { - CEL_RETURN_IF_ERROR(builder.Put(key, value)); - return true; - }, - descriptor_pool, message_factory, arena)); - - return std::move(builder).BuildCompat(); -} - MutableMapValue* absl_nonnull NewMutableMapValue( google::protobuf::Arena* absl_nonnull arena) { - return ::new (arena->AllocateAligned(sizeof(MutableCompatMapValueImpl), - alignof(MutableCompatMapValueImpl))) - MutableCompatMapValueImpl(arena); + return ::new (arena->AllocateAligned(sizeof(MutableMapValueImpl), + alignof(MutableMapValueImpl))) + MutableMapValueImpl(arena); } bool IsMutableMapValue(const Value& value) { if (auto custom_map_value = value.AsCustomMap(); custom_map_value) { - NativeTypeId native_type_id = custom_map_value->GetTypeId(); - if (native_type_id == NativeTypeId::For() || - native_type_id == NativeTypeId::For()) { - return true; - } + return custom_map_value->GetTypeId() == + NativeTypeId::For(); } return false; } bool IsMutableMapValue(const MapValue& value) { if (auto custom_map_value = value.AsCustom(); custom_map_value) { - NativeTypeId native_type_id = custom_map_value->GetTypeId(); - if (native_type_id == NativeTypeId::For() || - native_type_id == NativeTypeId::For()) { - return true; - } + return custom_map_value->GetTypeId() == + NativeTypeId::For(); } return false; } const MutableMapValue* absl_nullable AsMutableMapValue(const Value& value) { if (auto custom_map_value = value.AsCustomMap(); custom_map_value) { - NativeTypeId native_type_id = custom_map_value->GetTypeId(); - if (native_type_id == NativeTypeId::For()) { + if (custom_map_value->GetTypeId() == NativeTypeId::For()) { return cel::internal::down_cast( custom_map_value->interface()); } - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - custom_map_value->interface()); - } } return nullptr; } const MutableMapValue* absl_nullable AsMutableMapValue(const MapValue& value) { if (auto custom_map_value = value.AsCustom(); custom_map_value) { - NativeTypeId native_type_id = custom_map_value->GetTypeId(); - if (native_type_id == NativeTypeId::For()) { + if (custom_map_value->GetTypeId() == NativeTypeId::For()) { return cel::internal::down_cast( custom_map_value->interface()); } - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - custom_map_value->interface()); - } } return nullptr; } const MutableMapValue& GetMutableMapValue(const Value& value) { ABSL_DCHECK(IsMutableMapValue(value)) << value; - const auto& custom_map_value = value.GetCustomMap(); - NativeTypeId native_type_id = custom_map_value.GetTypeId(); - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - *custom_map_value.interface()); - } - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - *custom_map_value.interface()); - } - ABSL_UNREACHABLE(); + return cel::internal::down_cast( + *value.GetCustomMap().interface()); } const MutableMapValue& GetMutableMapValue(const MapValue& value) { ABSL_DCHECK(IsMutableMapValue(value)) << value; - const auto& custom_map_value = value.GetCustom(); - NativeTypeId native_type_id = custom_map_value.GetTypeId(); - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - *custom_map_value.interface()); - } - if (native_type_id == NativeTypeId::For()) { - return cel::internal::down_cast( - *custom_map_value.interface()); - } - ABSL_UNREACHABLE(); + return cel::internal::down_cast( + *value.GetCustom().interface()); } absl_nonnull cel::MapValueBuilderPtr NewMapValueBuilder( diff --git a/eval/compiler/flat_expr_builder.cc b/eval/compiler/flat_expr_builder.cc index 3626937e9..b026857ff 100644 --- a/eval/compiler/flat_expr_builder.cc +++ b/eval/compiler/flat_expr_builder.cc @@ -963,6 +963,10 @@ class FlatExprVisitor : public cel::AstVisitor { if (options_.enable_typed_field_access) { std::optional operand_type = extension_context_.GetResolvedType(select_expr.operand().id()); + if (enable_optional_types_ && operand_type.has_value() && + operand_type->IsOptional()) { + operand_type = operand_type->GetOptional().GetParameter(); + } if (operand_type.has_value() && operand_type->IsStruct()) { struct_type = operand_type->GetStruct(); if (struct_type.has_value()) { diff --git a/eval/compiler/flat_expr_builder_test.cc b/eval/compiler/flat_expr_builder_test.cc index 63c7c68cd..892c411ba 100644 --- a/eval/compiler/flat_expr_builder_test.cc +++ b/eval/compiler/flat_expr_builder_test.cc @@ -3843,6 +3843,199 @@ TEST(FlatExprBuilderTest, BindInlinesConstantAndFoldsMainExpression) { ASSERT_OK_AND_ASSIGN(CelValue result, cel_expr->Evaluate(activation, &arena)); EXPECT_THAT(result, test::IsCelInt64(16)); } + +class OptionalWrapFunction final : public cel::Function { + public: + explicit OptionalWrapFunction(bool empty) : empty_(empty) {} + + absl::StatusOr Invoke( + absl::Span args, + const InvokeContext& context) const override { + if (empty_) { + return cel::OptionalValue::None(); + } + return cel::OptionalValue::Of(args[0], context.arena()); + } + + private: + bool empty_; +}; + +class OptionalUnwrapOrSentinelFunction final : public cel::Function { + public: + absl::StatusOr Invoke( + absl::Span args, + const InvokeContext& context) const override { + if (!args[0].IsOptional()) { + return args[0]; + } + const cel::OptionalValue& opt = args[0].GetOptional(); + if (!opt.HasValue()) { + return cel::StringValue::Literal("optional.none"); + } + return opt.Value(); + } +}; + +TEST(FlatExprBuilderTest, ChainedTypedSelectOptionalAndHas) { + std::shared_ptr env = + NewTestingRuntimeEnv(); + cel::RuntimeOptions options; + options.enable_typed_field_access = true; + CelExpressionBuilderFlatImpl builder(env, options); + ASSERT_THAT(RegisterBuiltinFunctions(builder.GetRegistry()), IsOk()); + ASSERT_THAT(builder.GetRegistry()->Register( + cel::FunctionDescriptor("make_opt", false, {cel::Kind::kAny}), + std::make_unique(/*empty=*/false)), + IsOk()); + ASSERT_THAT(builder.GetRegistry()->Register( + cel::FunctionDescriptor("make_none", false, {}), + std::make_unique(/*empty=*/true)), + IsOk()); + ASSERT_THAT( + builder.GetRegistry()->Register( + cel::FunctionDescriptor("unwrap_opt", false, {cel::Kind::kAny}), + std::make_unique()), + IsOk()); + builder.flat_expr_builder().enable_optional_types(); + + auto make_optional_msg_type = [](std::string msg_name) { + return cel::TypeSpec(cel::AbstractType( + "optional_type", + {cel::TypeSpec(cel::MessageTypeSpec(std::move(msg_name)))})); + }; + + auto build_select_chain = + [&](bool start_with_none, + bool test_only) -> absl::StatusOr { + cel::Expr ident_or_call; + if (start_with_none) { + ident_or_call.set_id(1); + ident_or_call.mutable_call_expr().set_function("make_none"); + } else { + ident_or_call.set_id(1); + auto& call = ident_or_call.mutable_call_expr(); + call.set_function("make_opt"); + auto& arg = call.mutable_args().emplace_back(); + arg.set_id(10); + arg.mutable_ident_expr().set_name("message"); + } + + cel::Expr select1; + select1.set_id(2); + select1.mutable_select_expr().set_field("single_nested_message"); + select1.mutable_select_expr().set_operand(std::move(ident_or_call)); + + cel::Expr select2; + select2.set_id(3); + select2.mutable_select_expr().set_field("bb"); + select2.mutable_select_expr().set_test_only(test_only); + select2.mutable_select_expr().set_operand(std::move(select1)); + + cel::Expr root; + if (test_only) { + root = std::move(select2); + } else { + root.set_id(4); + root.mutable_call_expr().set_function("unwrap_opt"); + root.mutable_call_expr().mutable_args().push_back(std::move(select2)); + } + + cel::Ast::TypeMap type_map; + type_map[1] = + make_optional_msg_type("cel.expr.conformance.proto3.TestAllTypes"); + type_map[2] = make_optional_msg_type( + "cel.expr.conformance.proto3.TestAllTypes.NestedMessage"); + if (!start_with_none) { + type_map[10] = cel::TypeSpec( + cel::MessageTypeSpec("cel.expr.conformance.proto3.TestAllTypes")); + } + if (test_only) { + type_map[3] = cel::TypeSpec(cel::PrimitiveType::kBool); + } else { + type_map[3] = cel::TypeSpec(cel::AbstractType( + "optional_type", {cel::TypeSpec(cel::PrimitiveType::kInt64)})); + } + + auto ast = std::make_unique(std::move(root), cel::SourceInfo(), + cel::Ast::ReferenceMap(), + std::move(type_map), "0"); + return builder.flat_expr_builder().CreateExpressionImpl(std::move(ast), + nullptr); + }; + + google::protobuf::Arena arena; + { + ASSERT_OK_AND_ASSIGN(FlatExpression flat_expr, + build_select_chain(/*start_with_none=*/false, + /*test_only=*/false)); + CelExpressionFlatImpl cel_expr(env, std::move(flat_expr)); + + cel::expr::conformance::proto3::TestAllTypes message; + { + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena)); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena)); + EXPECT_THAT(result, test::IsCelString("optional.none")); + } + message.mutable_single_nested_message()->set_bb(55); + { + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena)); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena)); + EXPECT_THAT(result, test::IsCelInt64(55)); + } + } + { + ASSERT_OK_AND_ASSIGN(FlatExpression flat_expr, + build_select_chain(/*start_with_none=*/true, + /*test_only=*/false)); + CelExpressionFlatImpl cel_expr(env, std::move(flat_expr)); + Activation activation; + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena)); + EXPECT_THAT(result, test::IsCelString("optional.none")); + } + { + ASSERT_OK_AND_ASSIGN(FlatExpression flat_expr, + build_select_chain(/*start_with_none=*/false, + /*test_only=*/true)); + CelExpressionFlatImpl cel_expr(env, std::move(flat_expr)); + + cel::expr::conformance::proto3::TestAllTypes message; + { + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena)); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena)); + EXPECT_THAT(result, test::IsCelBool(false)); + } + message.mutable_single_nested_message()->set_bb(55); + { + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena)); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena)); + EXPECT_THAT(result, test::IsCelBool(true)); + } + } + { + ASSERT_OK_AND_ASSIGN(FlatExpression flat_expr, + build_select_chain(/*start_with_none=*/true, + /*test_only=*/true)); + CelExpressionFlatImpl cel_expr(env, std::move(flat_expr)); + Activation activation; + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena)); + EXPECT_THAT(result, test::IsCelBool(false)); + } +} } // namespace } // namespace google::api::expr::runtime diff --git a/eval/eval/BUILD b/eval/eval/BUILD index c19252d02..e57407cc1 100644 --- a/eval/eval/BUILD +++ b/eval/eval/BUILD @@ -295,7 +295,6 @@ cc_library( ":expression_step_base", ":expression_step_logic", "//base:attributes", - "//common:legacy_value", "//common:type", "//common:value", "//common:value_kind", diff --git a/eval/eval/select_step.cc b/eval/eval/select_step.cc index 8a70e3b82..9fb42fa5a 100644 --- a/eval/eval/select_step.cc +++ b/eval/eval/select_step.cc @@ -11,7 +11,6 @@ #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "base/attribute.h" -#include "common/legacy_value.h" #include "common/type.h" #include "common/value.h" #include "common/value_kind.h" @@ -281,6 +280,52 @@ bool SupportsCachedFieldDescriptor( return true; } +// Attempts to unwrap `arg` into a `ParsedMessageValue` matching `descriptor` +// and `field_descriptor`. Populates `legacy_struct` when `arg` is a legacy +// `CelValue` message wrapper so field wrapping preserves legacy interop. +absl::optional TryUnwrapTypedMessage( + const Value& arg, const google::protobuf::Descriptor* descriptor, + const google::protobuf::FieldDescriptor* field_descriptor, + absl::optional& legacy_struct) { + if (auto unwrapped = arg.AsParsedMessage(); + unwrapped.has_value() && + SupportsCachedFieldDescriptor(*unwrapped, descriptor, field_descriptor)) { + legacy_struct = absl::nullopt; + return *unwrapped; + } + if (auto legacy = cel::common_internal::AsLegacyStructValue(arg); + legacy.has_value()) { + if (const google::protobuf::Message* legacy_message = legacy->GetLegacyMessage(); + legacy_message != nullptr) { + cel::ParsedMessageValue parsed_message = + cel::UnsafeParsedMessageValue(legacy_message); + if (SupportsCachedFieldDescriptor(parsed_message, descriptor, + field_descriptor)) { + legacy_struct = *legacy; + return parsed_message; + } + } + } + return absl::nullopt; +} + +absl::Status GetTypedMessageField( + const cel::ParsedMessageValue& parsed_message, + const absl::optional& + legacy_struct, + const google::protobuf::FieldDescriptor* field_descriptor, + ProtoWrapperTypeOptions unboxing_option, ExecutionFrame* frame, + Value* result) { + if (legacy_struct.has_value()) { + return legacy_struct->WrapLegacyMessageField( + field_descriptor, unboxing_option, frame->descriptor_pool(), + frame->message_factory(), frame->arena(), result); + } + return parsed_message.GetField( + field_descriptor, unboxing_option, frame->descriptor_pool(), + frame->message_factory(), frame->arena(), result); +} + class ProtoSelectStep : public SelectStep { public: ProtoSelectStep(absl::string_view value, @@ -304,72 +349,68 @@ class ProtoSelectStep : public SelectStep { } const Value& arg = frame->value_stack().Peek(); - if (auto unwrapped = arg.AsParsedMessage(); - unwrapped.has_value() && - SupportsCachedFieldDescriptor(*unwrapped, descriptor_, - field_descriptor_)) { - EvaluateMessageFieldGet(frame, *unwrapped); + absl::optional legacy_struct; + if (auto parsed_message = TryUnwrapTypedMessage( + arg, descriptor_, field_descriptor_, legacy_struct); + parsed_message.has_value()) { + if (CheckAttributeTrail(field_, frame)) { + return; + } + if (absl::Status status = GetTypedMessageField( + *parsed_message, legacy_struct, field_descriptor_, + unboxing_option_, frame, &frame->value_stack().Peek()); + !status.ok()) { + frame->Abort(std::move(status)); + } return; - } else if (const google::protobuf::Message* legacy_message = - cel::interop_internal::GetLegacyMessage(arg); - legacy_message != nullptr) { - auto parsed_message = cel::UnsafeParsedMessageValue(legacy_message); - // A little unfortunate, but need to special case for legacy values so we - // can minimize back and forth interop conversions. - if (SupportsCachedFieldDescriptor(parsed_message, descriptor_, - field_descriptor_)) { - EvaluateMessageFieldGet(frame, legacy_message); + } + + if (enable_optional_types_ && arg.IsOptional()) { + const OptionalValue& optional_arg = arg.GetOptional(); + if (!optional_arg.HasValue()) { + if (CheckAttributeTrail(field_, frame)) { + return; + } + frame->value_stack().Peek() = OptionalValue::None(); + return; + } + Value unwrapped_opt; + optional_arg.Value(&unwrapped_opt); + if (auto parsed_message = TryUnwrapTypedMessage( + unwrapped_opt, descriptor_, field_descriptor_, legacy_struct); + parsed_message.has_value()) { + if (CheckAttributeTrail(field_, frame)) { + return; + } + if (!parsed_message->HasField(field_descriptor_)) { + frame->value_stack().Peek() = OptionalValue::None(); + return; + } + Value field_result; + if (absl::Status status = GetTypedMessageField( + *parsed_message, legacy_struct, field_descriptor_, + unboxing_option_, frame, &field_result); + !status.ok()) { + frame->Abort(std::move(status)); + return; + } + ABSL_DCHECK(!field_result.IsUnknown()); + frame->value_stack().Peek() = + OptionalValue::Of(std::move(field_result), frame->arena()); return; } } + // If we get an unexpected value type, fall back to the generic // implementation. SelectStep::Evaluate(frame); } private: - void EvaluateMessageFieldGet( - ExecutionFrame* frame, - const cel::ParsedMessageValue& parsed_message) const; - void EvaluateMessageFieldGet( - ExecutionFrame* frame, - const google::protobuf::Message* absl_nonnull legacy_message) const; - const google::protobuf::Descriptor* descriptor_; const google::protobuf::FieldDescriptor* field_descriptor_; }; -void ProtoSelectStep::EvaluateMessageFieldGet( - ExecutionFrame* frame, - const cel::ParsedMessageValue& parsed_message) const { - if (CheckAttributeTrail(field_, frame)) { - return; - } - if (absl::Status status = parsed_message.GetField( - field_descriptor_, unboxing_option_, frame->descriptor_pool(), - frame->message_factory(), frame->arena(), - &frame->value_stack().Peek()); - !status.ok()) { - frame->Abort(std::move(status)); - } -} - -void ProtoSelectStep::EvaluateMessageFieldGet( - ExecutionFrame* frame, - const google::protobuf::Message* absl_nonnull legacy_message) const { - ABSL_DCHECK(legacy_message != nullptr); - if (CheckAttributeTrail(field_, frame)) { - return; - } - if (absl::Status status = cel::interop_internal::WrapLegacyMessageField( - legacy_message, field_descriptor_, unboxing_option_, - frame->descriptor_pool(), frame->message_factory(), frame->arena(), - &frame->value_stack().Peek()); - !status.ok()) { - frame->Abort(std::move(status)); - } -} - class ProtoHasStep : public SelectStep { public: ProtoHasStep(absl::string_view field, bool enable_wrapper_type_null_unboxing, @@ -391,23 +432,33 @@ class ProtoHasStep : public SelectStep { } const Value& arg = frame->value_stack().Peek(); - if (auto unwrapped = arg.AsParsedMessage(); - unwrapped.has_value() && - SupportsCachedFieldDescriptor(*unwrapped, descriptor_, - field_descriptor_)) { - EvaluateHas(frame, *unwrapped); + absl::optional legacy_struct; + if (auto parsed_message = TryUnwrapTypedMessage( + arg, descriptor_, field_descriptor_, legacy_struct); + parsed_message.has_value()) { + EvaluateHas(frame, *parsed_message); return; - } else if (const google::protobuf::Message* legacy_message = - cel::interop_internal::GetLegacyMessage(arg); - legacy_message != nullptr) { - cel::ParsedMessageValue parsed_message = - cel::UnsafeParsedMessageValue(legacy_message); - if (SupportsCachedFieldDescriptor(parsed_message, descriptor_, - field_descriptor_)) { - EvaluateHas(frame, parsed_message); + } + + if (enable_optional_types_ && arg.IsOptional()) { + const OptionalValue& optional_arg = arg.GetOptional(); + if (!optional_arg.HasValue()) { + if (CheckAttributeTrail(field_, frame)) { + return; + } + frame->value_stack().Peek() = cel::FalseValue(); + return; + } + Value unwrapped_opt; + optional_arg.Value(&unwrapped_opt); + if (auto parsed_message = TryUnwrapTypedMessage( + unwrapped_opt, descriptor_, field_descriptor_, legacy_struct); + parsed_message.has_value()) { + EvaluateHas(frame, *parsed_message); return; } } + // If we get an unexpected value type, fall back to the generic // implementation. SelectStep::Evaluate(frame); @@ -447,17 +498,15 @@ absl::StatusOr> CreateTypedSelectStep( absl::string_view field, cel::StructType resolved_operand_type, cel::StructTypeField resolved_field, bool test_only, bool enable_wrapper_type_null_unboxing, bool enable_optional_types) { - if (!resolved_operand_type.IsMessage()) { + if (!resolved_operand_type.IsMessage() || !resolved_field.IsMessage()) { // The specialization only supports messages. Fallback to the generic // implementation for other types. - // TODO(uncreated-issue/89): support optional select and chaining. return CreateSelectStep(field, test_only, enable_wrapper_type_null_unboxing, enable_optional_types); } const google::protobuf::Descriptor* descriptor = resolved_operand_type.GetMessage().descriptor(); - ABSL_DCHECK(resolved_field.IsMessage()); const google::protobuf::FieldDescriptor* field_descriptor = resolved_field.GetMessage().descriptor(); diff --git a/eval/eval/select_step_test.cc b/eval/eval/select_step_test.cc index 8a89529b5..29ac2f388 100644 --- a/eval/eval/select_step_test.cc +++ b/eval/eval/select_step_test.cc @@ -20,6 +20,7 @@ #include "common/expr.h" #include "common/legacy_value.h" #include "common/type.h" +#include "common/value.h" #include "eval/eval/cel_expression_flat_impl.h" #include "eval/eval/evaluator_core.h" #include "eval/public/activation.h" @@ -1053,6 +1054,389 @@ TEST_F(SelectStepTest, TypedSelectStepUnknownPatternResolvesToUnknown) { ASSERT_TRUE(result.IsUnknownSet()); } +// Helper step to wrap the top-of-stack value in `cel::OptionalValue::Of(...)` +// while preserving its `AttributeTrail` for unknown attribute tests. +class WrapOptionalTestStep : public ExpressionStepLogic { + public: + void Evaluate(ExecutionFrame* frame) const override { + cel::Value& top = frame->value_stack().Peek(); + top = cel::OptionalValue::Of(std::move(top), frame->arena()); + } +}; + +// Helper step to unwrap `cel::OptionalValue` into a sentinel `StringValue` +// ("optional.none") or its held value so `CelExpressionFlatImpl::Evaluate` can +// return it as a `CelValue`. +class UnwrapOptionalTestStep : public ExpressionStepLogic { + public: + void Evaluate(ExecutionFrame* frame) const override { + cel::Value& top = frame->value_stack().Peek(); + if (!top.IsOptional()) { + return; + } + const cel::OptionalValue& opt = top.GetOptional(); + if (!opt.HasValue()) { + top = cel::StringValue::Literal("optional.none"); + return; + } + cel::Value inner; + opt.Value(&inner); + top = std::move(inner); + } +}; + +TEST_P(SelectStepConformanceTest, TypedHasStepTest) { + cel::StructType resolved_operand_type( + (cel::MessageType(TestAllTypes::descriptor()))); + const google::protobuf::FieldDescriptor* field_desc = + TestAllTypes::descriptor()->FindFieldByName("single_int64"); + ASSERT_NE(field_desc, nullptr); + cel::StructTypeField resolved_field((cel::MessageTypeField(field_desc))); + + ASSERT_OK_AND_ASSIGN( + auto step1, CreateTypedSelectStep( + "single_int64", resolved_operand_type, resolved_field, + /*test_only=*/true, + /*enable_wrapper_type_null_unboxing=*/false, + /*enable_optional_types=*/false)); + + ExecutionPath path; + path.push_back(ExpressionStep::MakeIdentifierStep(0)); + path.push_back(ExpressionStep::MakeGenericStep(std::move(step1))); + cel::RuntimeOptions options; + if (GetParam()) { + options.unknown_processing = cel::UnknownProcessingOptions::kAttributeOnly; + } + CelExpressionFlatImpl cel_expr( + env_, + FlatExpression(std::move(path), /*comprehension_slot_count=*/1, + env_->type_registry.GetComposedTypeProvider(), options, + /*arena=*/nullptr, IdentifierTable{{"message", 0}})); + + TestAllTypes message; + { + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena_)); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsBool()); + EXPECT_FALSE(result.BoolOrDie()); + } + message.set_single_int64(42); + { + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena_)); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsBool()); + EXPECT_TRUE(result.BoolOrDie()); + } +} + +TEST_P(SelectStepConformanceTest, ChainedTypedSelectStepTest) { + cel::StructType root_type((cel::MessageType(TestAllTypes::descriptor()))); + const google::protobuf::FieldDescriptor* nested_field_desc = + TestAllTypes::descriptor()->FindFieldByName("single_nested_message"); + ASSERT_NE(nested_field_desc, nullptr); + cel::StructTypeField nested_field((cel::MessageTypeField(nested_field_desc))); + + cel::StructType nested_type( + (cel::MessageType(TestAllTypes::NestedMessage::descriptor()))); + const google::protobuf::FieldDescriptor* bb_field_desc = + TestAllTypes::NestedMessage::descriptor()->FindFieldByName("bb"); + ASSERT_NE(bb_field_desc, nullptr); + cel::StructTypeField bb_field((cel::MessageTypeField(bb_field_desc))); + + ASSERT_OK_AND_ASSIGN( + auto step1, + CreateTypedSelectStep("single_nested_message", root_type, nested_field, + /*test_only=*/false, + /*enable_wrapper_type_null_unboxing=*/false, + /*enable_optional_types=*/false)); + ASSERT_OK_AND_ASSIGN( + auto step2, + CreateTypedSelectStep("bb", nested_type, bb_field, + /*test_only=*/false, + /*enable_wrapper_type_null_unboxing=*/false, + /*enable_optional_types=*/false)); + + ExecutionPath path; + path.push_back(ExpressionStep::MakeIdentifierStep(0)); + path.push_back(ExpressionStep::MakeGenericStep(std::move(step1))); + path.push_back(ExpressionStep::MakeGenericStep(std::move(step2))); + cel::RuntimeOptions options; + if (GetParam()) { + options.unknown_processing = cel::UnknownProcessingOptions::kAttributeOnly; + } + CelExpressionFlatImpl cel_expr( + env_, + FlatExpression(std::move(path), /*comprehension_slot_count=*/1, + env_->type_registry.GetComposedTypeProvider(), options, + /*arena=*/nullptr, IdentifierTable{{"message", 0}})); + + TestAllTypes message; + message.mutable_single_nested_message()->set_bb(99); + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena_)); + + ASSERT_OK_AND_ASSIGN(CelValue result, cel_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsInt64()); + EXPECT_EQ(result.Int64OrDie(), 99); +} + +TEST_P(SelectStepConformanceTest, TypedOptionalSelectStepTest) { + cel::StructType root_type((cel::MessageType(TestAllTypes::descriptor()))); + const google::protobuf::FieldDescriptor* nested_field_desc = + TestAllTypes::descriptor()->FindFieldByName("single_nested_message"); + ASSERT_NE(nested_field_desc, nullptr); + cel::StructTypeField nested_field((cel::MessageTypeField(nested_field_desc))); + + cel::StructType nested_type( + (cel::MessageType(TestAllTypes::NestedMessage::descriptor()))); + const google::protobuf::FieldDescriptor* bb_field_desc = + TestAllTypes::NestedMessage::descriptor()->FindFieldByName("bb"); + ASSERT_NE(bb_field_desc, nullptr); + cel::StructTypeField bb_field((cel::MessageTypeField(bb_field_desc))); + + auto make_chained_expr = + [&](bool start_with_none) -> absl::StatusOr { + ExecutionPath path; + if (start_with_none) { + path.push_back(ExpressionStep::MakeConstant(cel::OptionalValue::None())); + } else { + path.push_back(ExpressionStep::MakeIdentifierStep(0)); + path.push_back(ExpressionStep::MakeGenericStep( + std::make_unique(), -1, /*stack_delta=*/0)); + } + CEL_ASSIGN_OR_RETURN( + auto step1, + CreateTypedSelectStep("single_nested_message", root_type, nested_field, + /*test_only=*/false, + /*enable_wrapper_type_null_unboxing=*/false, + /*enable_optional_types=*/true)); + CEL_ASSIGN_OR_RETURN( + auto step2, + CreateTypedSelectStep("bb", nested_type, bb_field, /*test_only=*/false, + /*enable_wrapper_type_null_unboxing=*/false, + /*enable_optional_types=*/true)); + path.push_back(ExpressionStep::MakeGenericStep(std::move(step1))); + path.push_back(ExpressionStep::MakeGenericStep(std::move(step2))); + path.push_back(ExpressionStep::MakeGenericStep( + std::make_unique(), -1, /*stack_delta=*/0)); + + cel::RuntimeOptions options; + if (GetParam()) { + options.unknown_processing = + cel::UnknownProcessingOptions::kAttributeOnly; + } + return CelExpressionFlatImpl( + env_, + FlatExpression(std::move(path), /*comprehension_slot_count=*/1, + env_->type_registry.GetComposedTypeProvider(), options, + /*arena=*/nullptr, IdentifierTable{{"message", 0}})); + }; + + // Optional none propagates as optional.none. + { + ASSERT_OK_AND_ASSIGN(CelExpressionFlatImpl none_expr, + make_chained_expr(true)); + Activation activation; + ASSERT_OK_AND_ASSIGN(CelValue result, + none_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsString()); + EXPECT_EQ(result.StringOrDie().value(), "optional.none"); + } + + ASSERT_OK_AND_ASSIGN(CelExpressionFlatImpl cel_expr, + make_chained_expr(false)); + // Unset intermediate field returns optional.none. + { + TestAllTypes message; + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena_)); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsString()); + EXPECT_EQ(result.StringOrDie().value(), "optional.none"); + } + // Present intermediate message with unset leaf field returns optional.none. + { + TestAllTypes message; + message.mutable_single_nested_message(); + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena_)); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsString()); + EXPECT_EQ(result.StringOrDie().value(), "optional.none"); + } + // Present intermediate and leaf field returns optional.of(value). + { + TestAllTypes message; + message.mutable_single_nested_message()->set_bb(123); + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena_)); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsInt64()); + EXPECT_EQ(result.Int64OrDie(), 123); + } + // Fallback when optional holds a legacy map instead of a proto message. + { + std::string nested_key = "single_nested_message"; + std::string bb_key = "bb"; + std::vector> inner_kvs{ + {CelValue::CreateString(&bb_key), CelValue::CreateInt64(77)}}; + ASSERT_OK_AND_ASSIGN(auto inner_map, + CreateContainerBackedMap(absl::MakeSpan(inner_kvs))); + std::vector> outer_kvs{ + {CelValue::CreateString(&nested_key), + CelValue::CreateMap(inner_map.get())}}; + ASSERT_OK_AND_ASSIGN(auto outer_map, + CreateContainerBackedMap(absl::MakeSpan(outer_kvs))); + Activation activation; + activation.InsertValue("message", CelValue::CreateMap(outer_map.get())); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsInt64()); + EXPECT_EQ(result.Int64OrDie(), 77); + } +} + +TEST_F(SelectStepTest, TypedOptionalSelectStepUnknownPatternResolvesToUnknown) { + cel::StructType root_type((cel::MessageType(TestAllTypes::descriptor()))); + const google::protobuf::FieldDescriptor* field_desc = + TestAllTypes::descriptor()->FindFieldByName("single_int64"); + ASSERT_NE(field_desc, nullptr); + cel::StructTypeField resolved_field((cel::MessageTypeField(field_desc))); + + ASSERT_OK_AND_ASSIGN( + auto step1, + CreateTypedSelectStep("single_int64", root_type, resolved_field, + /*test_only=*/false, + /*enable_wrapper_type_null_unboxing=*/false, + /*enable_optional_types=*/true)); + + ExecutionPath path; + path.push_back(ExpressionStep::MakeIdentifierStep(0)); + path.push_back(ExpressionStep::MakeGenericStep( + std::make_unique(), -1, /*stack_delta=*/0)); + path.push_back(ExpressionStep::MakeGenericStep(std::move(step1))); + path.push_back(ExpressionStep::MakeGenericStep( + std::make_unique(), -1, /*stack_delta=*/0)); + + cel::RuntimeOptions options; + options.unknown_processing = cel::UnknownProcessingOptions::kAttributeOnly; + CelExpressionFlatImpl cel_expr( + env_, + FlatExpression(std::move(path), /*comprehension_slot_count=*/1, + env_->type_registry.GetComposedTypeProvider(), options, + /*arena=*/nullptr, IdentifierTable{{"message", 0}})); + + TestAllTypes message; + message.set_single_int64(42); + std::vector unknown_patterns; + unknown_patterns.push_back(CelAttributePattern( + "message", {CelAttributeQualifierPattern::OfString("single_int64")})); + + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena_)); + ASSERT_THAT(activation.SetUnknownAttributePatterns(unknown_patterns), IsOk()); + + ASSERT_OK_AND_ASSIGN(CelValue result, cel_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsUnknownSet()); +} + +TEST_P(SelectStepConformanceTest, TypedOptionalHasStepTest) { + cel::StructType root_type((cel::MessageType(TestAllTypes::descriptor()))); + const google::protobuf::FieldDescriptor* nested_field_desc = + TestAllTypes::descriptor()->FindFieldByName("single_nested_message"); + ASSERT_NE(nested_field_desc, nullptr); + cel::StructTypeField nested_field((cel::MessageTypeField(nested_field_desc))); + + cel::StructType nested_type( + (cel::MessageType(TestAllTypes::NestedMessage::descriptor()))); + const google::protobuf::FieldDescriptor* bb_field_desc = + TestAllTypes::NestedMessage::descriptor()->FindFieldByName("bb"); + ASSERT_NE(bb_field_desc, nullptr); + cel::StructTypeField bb_field((cel::MessageTypeField(bb_field_desc))); + + auto make_has_expr = + [&](bool start_with_none) -> absl::StatusOr { + ExecutionPath path; + if (start_with_none) { + path.push_back(ExpressionStep::MakeConstant(cel::OptionalValue::None())); + } else { + path.push_back(ExpressionStep::MakeIdentifierStep(0)); + path.push_back(ExpressionStep::MakeGenericStep( + std::make_unique(), -1, /*stack_delta=*/0)); + } + CEL_ASSIGN_OR_RETURN( + auto step1, + CreateTypedSelectStep("single_nested_message", root_type, nested_field, + /*test_only=*/false, + /*enable_wrapper_type_null_unboxing=*/false, + /*enable_optional_types=*/true)); + CEL_ASSIGN_OR_RETURN( + auto step2, + CreateTypedSelectStep("bb", nested_type, bb_field, /*test_only=*/true, + /*enable_wrapper_type_null_unboxing=*/false, + /*enable_optional_types=*/true)); + path.push_back(ExpressionStep::MakeGenericStep(std::move(step1))); + path.push_back(ExpressionStep::MakeGenericStep(std::move(step2))); + + cel::RuntimeOptions options; + if (GetParam()) { + options.unknown_processing = + cel::UnknownProcessingOptions::kAttributeOnly; + } + return CelExpressionFlatImpl( + env_, + FlatExpression(std::move(path), /*comprehension_slot_count=*/1, + env_->type_registry.GetComposedTypeProvider(), options, + /*arena=*/nullptr, IdentifierTable{{"message", 0}})); + }; + + { + ASSERT_OK_AND_ASSIGN(CelExpressionFlatImpl none_expr, make_has_expr(true)); + Activation activation; + ASSERT_OK_AND_ASSIGN(CelValue result, + none_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsBool()); + EXPECT_FALSE(result.BoolOrDie()); + } + + ASSERT_OK_AND_ASSIGN(CelExpressionFlatImpl cel_expr, make_has_expr(false)); + { + TestAllTypes message; + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena_)); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsBool()); + EXPECT_FALSE(result.BoolOrDie()); + } + { + TestAllTypes message; + message.mutable_single_nested_message()->set_bb(10); + Activation activation; + activation.InsertValue("message", + CelProtoWrapper::CreateMessage(&message, &arena_)); + ASSERT_OK_AND_ASSIGN(CelValue result, + cel_expr.Evaluate(activation, &arena_)); + ASSERT_TRUE(result.IsBool()); + EXPECT_TRUE(result.BoolOrDie()); + } +} + INSTANTIATE_TEST_SUITE_P(UnknownsEnabled, SelectStepConformanceTest, testing::Bool()); diff --git a/eval/public/structs/BUILD b/eval/public/structs/BUILD index 1eb7d0772..afeb20038 100644 --- a/eval/public/structs/BUILD +++ b/eval/public/structs/BUILD @@ -31,6 +31,7 @@ cc_library( ":cel_proto_wrap_value_to_message", ":proto_message_type_adapter", ":trivial_legacy_type_info_internal", + "//common:legacy_value", "//common:value", "//eval/public:cel_value", "//eval/public:message_wrapper", @@ -128,6 +129,7 @@ cc_library( ], deps = [ "//eval/public:cel_value", + "//internal:json", "//internal:overflow", "//internal:proto_time_encoding", "//internal:status_macros", @@ -364,6 +366,7 @@ cc_test( ":proto_message_type_adapter", ":trivial_legacy_type_info", "//base:attributes", + "//common:legacy_value", "//common:value", "//common:value_testing", "//eval/public:cel_value", diff --git a/eval/public/structs/cel_proto_wrap_value_to_message.cc b/eval/public/structs/cel_proto_wrap_value_to_message.cc index 0b04e93d2..fb36bea92 100644 --- a/eval/public/structs/cel_proto_wrap_value_to_message.cc +++ b/eval/public/structs/cel_proto_wrap_value_to_message.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include "google/protobuf/any.pb.h" #include "google/protobuf/duration.pb.h" @@ -34,6 +35,7 @@ #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "eval/public/cel_value.h" +#include "internal/json.h" #include "internal/overflow.h" #include "internal/proto_time_encoding.h" #include "internal/status_macros.h" @@ -41,7 +43,6 @@ #include "internal/well_known_types.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" -#include "google/protobuf/json/json.h" #include "google/protobuf/message.h" namespace google::api::expr::runtime::internal { @@ -63,8 +64,6 @@ using google::protobuf::Arena; using google::protobuf::Descriptor; using google::protobuf::Message; using google::protobuf::MessageFactory; -using google::protobuf::json::MessageToJsonString; -using google::protobuf::json::PrintOptions; // kMaxIntJSON is defined as the Number.MAX_SAFE_INTEGER value per EcmaScript 6. constexpr int64_t kMaxIntJSON = (1ll << 53) - 1; @@ -94,23 +93,28 @@ static bool IsFieldMaskProto(const google::protobuf::Descriptor* descriptor) { static std::optional GetFieldMaskJsonString( const google::protobuf::Message& message) { - // TODO(b/540507668): Refactor to pipe descriptor_pool through - // ValueFromValue to use internal::MessageToJson. - PrintOptions json_options; - std::string json_str; - auto status = MessageToJsonString(message, &json_str, json_options); + Value json_value; + const google::protobuf::Descriptor* descriptor = message.GetDescriptor(); + const google::protobuf::Reflection* reflection = message.GetReflection(); + if (descriptor == nullptr || reflection == nullptr) { + ABSL_LOG(ERROR) << "Failed to convert FieldMask to JSON: missing " + "descriptor or reflection"; + return std::nullopt; + } + const google::protobuf::DescriptorPool* pool = descriptor->file()->pool(); + google::protobuf::MessageFactory* factory = reflection->GetMessageFactory(); + if (pool == nullptr || factory == nullptr) { + ABSL_LOG(ERROR) << "Failed to convert FieldMask to JSON: missing " + "descriptor pool or message factory"; + return std::nullopt; + } + absl::Status status = + cel::internal::MessageToJson(message, pool, factory, &json_value); if (!status.ok()) { ABSL_LOG(ERROR) << "Failed to convert FieldMask to JSON: " << status; return std::nullopt; } - // If JSON marshalling is correct, we know we'll always get a plain - // JSON string value and it shouldn't contain any escapes that we need - // to interpret. - if (json_str.size() >= 2 && json_str.front() == '"' && - json_str.back() == '"') { - return json_str.substr(1, json_str.size() - 2); - } - return json_str; + return std::move(*json_value.mutable_string_value()); } struct IgnoreErrorAndReturnNullptr { diff --git a/eval/public/structs/cel_proto_wrap_value_to_message_test.cc b/eval/public/structs/cel_proto_wrap_value_to_message_test.cc index 40b9398e2..26238231b 100644 --- a/eval/public/structs/cel_proto_wrap_value_to_message_test.cc +++ b/eval/public/structs/cel_proto_wrap_value_to_message_test.cc @@ -505,6 +505,38 @@ TEST_F(CelProtoWrapValueToMessageTest, WrapFieldMaskToValue) { ExpectWrappedMessage(value, expected_message); } +TEST_F(CelProtoWrapValueToMessageTest, WrapFieldMaskSnakeCaseToValue) { + FieldMask field_mask; + ASSERT_TRUE(TextFormat::ParseFromString( + R"pb(paths: "foo_bar.baz_qux" paths: "some_field")pb", &field_mask)); + CelValue value = MessageToCelValue(&field_mask); + + Value expected_message; + ASSERT_TRUE(TextFormat::ParseFromString( + R"pb(string_value: "fooBar.bazQux,someField")pb", &expected_message)); + + ExpectWrappedMessage(value, expected_message); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapEmptyFieldMaskToValue) { + FieldMask field_mask; + CelValue value = MessageToCelValue(&field_mask); + + Value expected_message; + expected_message.set_string_value(""); + + ExpectWrappedMessage(value, expected_message); +} + +TEST_F(CelProtoWrapValueToMessageTest, WrapInvalidFieldMaskToValue) { + FieldMask field_mask; + field_mask.add_paths("Foo"); + CelValue value = MessageToCelValue(&field_mask); + + Value expected_message; + ExpectNotWrapped(value, expected_message); +} + TEST_F(CelProtoWrapValueToMessageTest, WrapMapWithFieldMaskToAny) { const std::string kField = "field_mask"; FieldMask field_mask; diff --git a/extensions/BUILD b/extensions/BUILD index d9f8d7684..b9324ccaa 100644 --- a/extensions/BUILD +++ b/extensions/BUILD @@ -568,6 +568,7 @@ cc_test( "//checker:validation_result", "//common:ast", "//common:decl", + "//common:legacy_value", "//common:minimal_descriptor_pool", "//common:type", "//common:value", diff --git a/extensions/select_optimization.cc b/extensions/select_optimization.cc index 97146d1a6..da47ecb43 100644 --- a/extensions/select_optimization.cc +++ b/extensions/select_optimization.cc @@ -47,7 +47,6 @@ #include "common/function_descriptor.h" #include "common/internal/attribute_trail.h" #include "common/kind.h" -#include "common/legacy_value.h" #include "common/native_type.h" #include "common/type.h" #include "common/value.h" @@ -838,10 +837,9 @@ absl::Status OptimizedSelectImpl::ApplySelect(ExecutionFrameBase& frame, if (result_in_out.IsParsedMessage()) { ParsedMessageValue parsed_message = result_in_out.GetParsedMessage(); current_message = parsed_message.message(); - is_unsafe = - cel::interop_internal::IsUnsafeParsedMessageValue(parsed_message); + is_unsafe = parsed_message.is_unsafe(); } else if (const google::protobuf::Message* legacy_message = - cel::interop_internal::GetLegacyMessage(result_in_out); + cel::common_internal::GetLegacyMessage(result_in_out); legacy_message != nullptr) { current_message = legacy_message; is_unsafe = true; diff --git a/runtime/standard/BUILD b/runtime/standard/BUILD index d08ac2116..f78c02f57 100644 --- a/runtime/standard/BUILD +++ b/runtime/standard/BUILD @@ -33,14 +33,20 @@ cc_library( ], deps = [ "//base:builtins", - "//base:function_adapter", + "//common:function_descriptor", + "//common:kind", "//common:value", + "//common:value_kind", "//internal:number", "//internal:status_macros", + "//runtime:function", "//runtime:function_registry", "//runtime:runtime_options", "@com_google_absl//absl/status", - "@com_google_absl//absl/time", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings:string_view", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", ], ) @@ -69,18 +75,19 @@ cc_library( ], deps = [ "//base:builtins", - "//base:function_adapter", + "//common:function_descriptor", + "//common:kind", "//common:value", + "//common:value_kind", "//internal:number", "//internal:status_macros", + "//runtime:function", "//runtime:function_registry", - "//runtime:register_function_helper", "//runtime:runtime_options", - "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_protobuf//:protobuf", + "@com_google_absl//absl/types:span", ], ) @@ -108,22 +115,23 @@ cc_library( hdrs = ["equality_functions.h"], deps = [ "//base:builtins", - "//base:function_adapter", + "//common:function_descriptor", + "//common:kind", "//common:value", "//common:value_kind", "//internal:number", "//internal:status_macros", + "//runtime:function", "//runtime:function_registry", - "//runtime:register_function_helper", "//runtime:runtime_options", "//runtime/internal:errors", "@com_google_absl//absl/base:nullability", - "@com_google_absl//absl/functional:function_ref", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", "@com_google_protobuf//:protobuf", ], ) @@ -156,16 +164,17 @@ cc_library( ], deps = [ "//base:builtins", - "//base:function_adapter", + "//common:function_descriptor", + "//common:kind", "//common:value", "//internal:status_macros", "//runtime:function", "//runtime:function_registry", - "//runtime:register_function_helper", "//runtime:runtime_options", "//runtime/internal:errors", "@com_google_absl//absl/status", - "@com_google_absl//absl/strings", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/types:span", ], ) @@ -203,14 +212,18 @@ cc_library( hdrs = ["container_functions.h"], deps = [ "//base:builtins", - "//base:function_adapter", + "//common:function_descriptor", + "//common:kind", "//common:value", + "//common:value_kind", "//internal:status_macros", + "//runtime:function", "//runtime:function_registry", "//runtime:runtime_options", "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/types:span", "@com_google_protobuf//:protobuf", ], ) @@ -235,8 +248,10 @@ cc_library( hdrs = ["type_conversion_functions.h"], deps = [ "//base:builtins", - "//base:function_adapter", + "//common:function_descriptor", + "//common:kind", "//common:value", + "//common:value_kind", "//internal:overflow", "//internal:status_macros", "//internal:time", @@ -247,8 +262,8 @@ cc_library( "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", "@com_google_protobuf//:protobuf", ], ) @@ -273,15 +288,19 @@ cc_library( hdrs = ["arithmetic_functions.h"], deps = [ "//base:builtins", - "//base:function_adapter", + "//common:function_descriptor", + "//common:kind", "//common:value", + "//common:value_kind", "//internal:overflow", "//internal:status_macros", "//runtime:function", "//runtime:function_registry", "//runtime:runtime_options", "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", ], ) @@ -305,8 +324,10 @@ cc_library( hdrs = ["time_functions.h"], deps = [ "//base:builtins", - "//base:function_adapter", + "//common:function_descriptor", + "//common:kind", "//common:value", + "//common:value_kind", "//internal:overflow", "//internal:status_macros", "//runtime:function", @@ -316,7 +337,7 @@ cc_library( "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/time", - "@com_google_protobuf//:protobuf", + "@com_google_absl//absl/types:span", ], ) @@ -340,16 +361,18 @@ cc_library( hdrs = ["string_functions.h"], deps = [ "//base:builtins", - "//base:function_adapter", + "//common:function_descriptor", + "//common:kind", "//common:value", + "//common:value_kind", "//internal:status_macros", + "//runtime:function", "//runtime:function_registry", "//runtime:runtime_options", - "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_protobuf//:protobuf", + "@com_google_absl//absl/types:span", ], ) @@ -373,7 +396,8 @@ cc_library( hdrs = ["regex_functions.h"], deps = [ "//base:builtins", - "//base:function_adapter", + "//common:function_descriptor", + "//common:kind", "//common:value", "//internal:re2_options", "//internal:status_macros", @@ -381,7 +405,8 @@ cc_library( "//runtime:function_registry", "//runtime:runtime_options", "@com_google_absl//absl/status", - "@com_google_absl//absl/strings", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/types:span", "@com_googlesource_code_re2//:re2", ], ) diff --git a/runtime/standard/arithmetic_functions.cc b/runtime/standard/arithmetic_functions.cc index c77dd1a12..e44a31e3f 100644 --- a/runtime/standard/arithmetic_functions.cc +++ b/runtime/standard/arithmetic_functions.cc @@ -16,12 +16,17 @@ #include #include +#include #include "absl/status/status.h" +#include "absl/status/statusor.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "base/builtins.h" -#include "base/function_adapter.h" +#include "common/function_descriptor.h" +#include "common/kind.h" #include "common/value.h" +#include "common/value_kind.h" #include "internal/overflow.h" #include "internal/status_macros.h" #include "runtime/function.h" @@ -31,214 +36,174 @@ namespace cel { namespace { -// Template functions providing arithmetic operations -template -Value Add(Type v0, Type v1, const Function::InvokeContext& context); - -template <> -Value Add(int64_t v0, int64_t v1, - const Function::InvokeContext& context) { - auto sum = cel::internal::CheckedAdd(v0, v1); - if (!sum.ok()) { - return ErrorValue::From(sum.status(), context.arena()); - } - return IntValue(*sum); -} - -template <> -Value Add(uint64_t v0, uint64_t v1, - const Function::InvokeContext& context) { - auto sum = cel::internal::CheckedAdd(v0, v1); - if (!sum.ok()) { - return ErrorValue::From(sum.status(), context.arena()); - } - return UintValue(*sum); -} - -template <> -Value Add(double v0, double v1, const Function::InvokeContext&) { - return DoubleValue(v0 + v1); -} - -template -Value Sub(Type v0, Type v1, const Function::InvokeContext& context); - -template <> -Value Sub(int64_t v0, int64_t v1, - const Function::InvokeContext& context) { - auto diff = cel::internal::CheckedSub(v0, v1); - if (!diff.ok()) { - return ErrorValue::From(diff.status(), context.arena()); - } - return IntValue(*diff); -} - -template <> -Value Sub(uint64_t v0, uint64_t v1, - const Function::InvokeContext& context) { - auto diff = cel::internal::CheckedSub(v0, v1); - if (!diff.ok()) { - return ErrorValue::From(diff.status(), context.arena()); - } - return UintValue(*diff); -} - -template <> -Value Sub(double v0, double v1, const Function::InvokeContext&) { - return DoubleValue(v0 - v1); -} - -template -Value Mul(Type v0, Type v1, const Function::InvokeContext& context); - -template <> -Value Mul(int64_t v0, int64_t v1, - const Function::InvokeContext& context) { - auto prod = cel::internal::CheckedMul(v0, v1); - if (!prod.ok()) { - return ErrorValue::From(prod.status(), context.arena()); - } - return IntValue(*prod); -} - -template <> -Value Mul(uint64_t v0, uint64_t v1, - const Function::InvokeContext& context) { - auto prod = cel::internal::CheckedMul(v0, v1); - if (!prod.ok()) { - return ErrorValue::From(prod.status(), context.arena()); - } - return UintValue(*prod); -} - -template <> -Value Mul(double v0, double v1, const Function::InvokeContext&) { - return DoubleValue(v0 * v1); -} - -template -Value Div(Type v0, Type v1, const Function::InvokeContext& context); - -// Division operations for integer types should check for -// division by 0 -template <> -Value Div(int64_t v0, int64_t v1, - const Function::InvokeContext& context) { - auto quot = cel::internal::CheckedDiv(v0, v1); - if (!quot.ok()) { - return ErrorValue::From(quot.status(), context.arena()); +class ArithmeticBinaryFunction final : public Function { + public: + enum class Op { kAdd, kSubtract, kMultiply, kDivide, kModulo }; + + explicit ArithmeticBinaryFunction(Op op) : op_(op) {} + + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + if (args.size() != 2) { + return absl::InvalidArgumentError( + "unexpected number of arguments for binary function"); + } + if (args[0].kind() != args[1].kind()) { + return absl::InvalidArgumentError( + "unexpected argument kinds for arithmetic function"); + } + switch (args[0].kind()) { + case ValueKind::kInt: { + int64_t lhs = args[0].GetInt().NativeValue(); + int64_t rhs = args[1].GetInt().NativeValue(); + absl::StatusOr result; + switch (op_) { + case Op::kAdd: + result = cel::internal::CheckedAdd(lhs, rhs); + break; + case Op::kSubtract: + result = cel::internal::CheckedSub(lhs, rhs); + break; + case Op::kMultiply: + result = cel::internal::CheckedMul(lhs, rhs); + break; + case Op::kDivide: + result = cel::internal::CheckedDiv(lhs, rhs); + break; + case Op::kModulo: + result = cel::internal::CheckedMod(lhs, rhs); + break; + } + if (!result.ok()) { + return ErrorValue::From(result.status(), context.arena()); + } + return IntValue(*result); + } + case ValueKind::kUint: { + uint64_t lhs = args[0].GetUint().NativeValue(); + uint64_t rhs = args[1].GetUint().NativeValue(); + absl::StatusOr result; + switch (op_) { + case Op::kAdd: + result = cel::internal::CheckedAdd(lhs, rhs); + break; + case Op::kSubtract: + result = cel::internal::CheckedSub(lhs, rhs); + break; + case Op::kMultiply: + result = cel::internal::CheckedMul(lhs, rhs); + break; + case Op::kDivide: + result = cel::internal::CheckedDiv(lhs, rhs); + break; + case Op::kModulo: + result = cel::internal::CheckedMod(lhs, rhs); + break; + } + if (!result.ok()) { + return ErrorValue::From(result.status(), context.arena()); + } + return UintValue(*result); + } + case ValueKind::kDouble: { + static_assert(std::numeric_limits::is_iec559, + "Division by zero for doubles must be supported"); + double lhs = args[0].GetDouble().NativeValue(); + double rhs = args[1].GetDouble().NativeValue(); + switch (op_) { + case Op::kAdd: + return DoubleValue(lhs + rhs); + case Op::kSubtract: + return DoubleValue(lhs - rhs); + case Op::kMultiply: + return DoubleValue(lhs * rhs); + case Op::kDivide: + // For double, division will result in +/- inf + return DoubleValue(lhs / rhs); + case Op::kModulo: + return absl::InvalidArgumentError( + "modulo is not defined for double"); + } + } + default: + return absl::InvalidArgumentError("expected numeric value"); + } } - return IntValue(*quot); -} -// Division operations for integer types should check for -// division by 0 -template <> -Value Div(uint64_t v0, uint64_t v1, - const Function::InvokeContext& context) { - auto quot = cel::internal::CheckedDiv(v0, v1); - if (!quot.ok()) { - return ErrorValue::From(quot.status(), context.arena()); + private: + Op op_; +}; + +class ArithmeticNegateFunction final : public Function { + public: + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + if (args.size() != 1) { + return absl::InvalidArgumentError( + "unexpected number of arguments for unary function"); + } + switch (args[0].kind()) { + case ValueKind::kInt: { + auto inv = + cel::internal::CheckedNegation(args[0].GetInt().NativeValue()); + if (!inv.ok()) { + return ErrorValue::From(inv.status(), context.arena()); + } + return IntValue(*inv); + } + case ValueKind::kDouble: + return DoubleValue(-args[0].GetDouble().NativeValue()); + default: + return absl::InvalidArgumentError("expected int or double value"); + } } - return UintValue(*quot); -} - -template <> -Value Div(double v0, double v1, const Function::InvokeContext&) { - static_assert(std::numeric_limits::is_iec559, - "Division by zero for doubles must be supported"); - - // For double, division will result in +/- inf - return DoubleValue(v0 / v1); -} - -// Modulo operation -template -Value Modulo(Type v0, Type v1, const Function::InvokeContext& context); - -// Modulo operations for integer types should check for -// division by 0 -template <> -Value Modulo(int64_t v0, int64_t v1, - const Function::InvokeContext& context) { - auto mod = cel::internal::CheckedMod(v0, v1); - if (!mod.ok()) { - return ErrorValue::From(mod.status(), context.arena()); - } - return IntValue(*mod); -} - -template <> -Value Modulo(uint64_t v0, uint64_t v1, - const Function::InvokeContext& context) { - auto mod = cel::internal::CheckedMod(v0, v1); - if (!mod.ok()) { - return ErrorValue::From(mod.status(), context.arena()); +}; + +absl::Status RegisterBinaryOverloads(FunctionRegistry& registry, + absl::string_view name, + ArithmeticBinaryFunction::Op op, + absl::Span kinds) { + for (Kind kind : kinds) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(name, /*is_receiver_style=*/false, {kind, kind}), + std::make_unique(op))); } - return UintValue(*mod); -} - -// Helper method -// Registers all arithmetic functions for template parameter type. -template -absl::Status RegisterArithmeticFunctionsForType(FunctionRegistry& registry) { - using FunctionAdapter = cel::BinaryFunctionAdapter; - CEL_RETURN_IF_ERROR(registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kAdd, false), - FunctionAdapter::WrapFunction(&Add))); - - CEL_RETURN_IF_ERROR(registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kSubtract, false), - FunctionAdapter::WrapFunction(&Sub))); - - CEL_RETURN_IF_ERROR(registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kMultiply, false), - FunctionAdapter::WrapFunction(&Mul))); - - return registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kDivide, false), - FunctionAdapter::WrapFunction(&Div)); + return absl::OkStatus(); } } // namespace absl::Status RegisterArithmeticFunctions(FunctionRegistry& registry, const RuntimeOptions& options) { - CEL_RETURN_IF_ERROR(RegisterArithmeticFunctionsForType(registry)); - CEL_RETURN_IF_ERROR(RegisterArithmeticFunctionsForType(registry)); - CEL_RETURN_IF_ERROR(RegisterArithmeticFunctionsForType(registry)); - - // Modulo - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter::CreateDescriptor( - cel::builtin::kModulo, false), - BinaryFunctionAdapter::WrapFunction( - &Modulo))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter::CreateDescriptor( - cel::builtin::kModulo, false), - BinaryFunctionAdapter::WrapFunction( - &Modulo))); + static constexpr Kind kNumericKinds[] = {Kind::kInt, Kind::kUint, + Kind::kDouble}; + static constexpr Kind kIntegralKinds[] = {Kind::kInt, Kind::kUint}; + + CEL_RETURN_IF_ERROR(RegisterBinaryOverloads( + registry, cel::builtin::kAdd, ArithmeticBinaryFunction::Op::kAdd, + kNumericKinds)); + CEL_RETURN_IF_ERROR(RegisterBinaryOverloads( + registry, cel::builtin::kSubtract, + ArithmeticBinaryFunction::Op::kSubtract, kNumericKinds)); + CEL_RETURN_IF_ERROR(RegisterBinaryOverloads( + registry, cel::builtin::kMultiply, + ArithmeticBinaryFunction::Op::kMultiply, kNumericKinds)); + CEL_RETURN_IF_ERROR(RegisterBinaryOverloads( + registry, cel::builtin::kDivide, ArithmeticBinaryFunction::Op::kDivide, + kNumericKinds)); + CEL_RETURN_IF_ERROR(RegisterBinaryOverloads( + registry, cel::builtin::kModulo, ArithmeticBinaryFunction::Op::kModulo, + kIntegralKinds)); // Negation group CEL_RETURN_IF_ERROR(registry.Register( - UnaryFunctionAdapter::CreateDescriptor(cel::builtin::kNeg, - false), - UnaryFunctionAdapter::WrapFunction( - [](int64_t value, const Function::InvokeContext& context) -> Value { - auto inv = cel::internal::CheckedNegation(value); - if (!inv.ok()) { - return ErrorValue::From(inv.status(), context.arena()); - } - return IntValue(*inv); - }))); - + FunctionDescriptor(cel::builtin::kNeg, /*is_receiver_style=*/false, + {Kind::kInt}), + std::make_unique())); return registry.Register( - UnaryFunctionAdapter::CreateDescriptor(cel::builtin::kNeg, - false), - UnaryFunctionAdapter::WrapFunction( - [](double value) -> double { return -value; })); + FunctionDescriptor(cel::builtin::kNeg, /*is_receiver_style=*/false, + {Kind::kDouble}), + std::make_unique()); } } // namespace cel diff --git a/runtime/standard/comparison_functions.cc b/runtime/standard/comparison_functions.cc index bddd1efe9..fd0c4abb6 100644 --- a/runtime/standard/comparison_functions.cc +++ b/runtime/standard/comparison_functions.cc @@ -14,15 +14,22 @@ #include "runtime/standard/comparison_functions.h" -#include +#include +#include #include "absl/status/status.h" -#include "absl/time/time.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/types/span.h" #include "base/builtins.h" -#include "base/function_adapter.h" +#include "common/function_descriptor.h" +#include "common/kind.h" #include "common/value.h" +#include "common/value_kind.h" #include "internal/number.h" #include "internal/status_macros.h" +#include "runtime/function.h" #include "runtime/function_registry.h" #include "runtime/runtime_options.h" @@ -32,231 +39,148 @@ namespace { using ::cel::internal::Number; -// Comparison template functions -template -bool LessThan(Type t1, Type t2) { - return (t1 < t2); -} - -template -bool LessThanOrEqual(Type t1, Type t2) { - return (t1 <= t2); -} - -template -bool GreaterThan(Type t1, Type t2) { - return LessThan(t2, t1); -} - -template -bool GreaterThanOrEqual(Type t1, Type t2) { - return LessThanOrEqual(t2, t1); -} - -// String value comparions specializations -template <> -bool LessThan(const StringValue& t1, const StringValue& t2) { - return t1.Compare(t2) < 0; -} - -template <> -bool LessThanOrEqual(const StringValue& t1, const StringValue& t2) { - return t1.Compare(t2) <= 0; -} - -template <> -bool GreaterThan(const StringValue& t1, const StringValue& t2) { - return t1.Compare(t2) > 0; -} - -template <> -bool GreaterThanOrEqual(const StringValue& t1, const StringValue& t2) { - return t1.Compare(t2) >= 0; -} - -// bytes value comparions specializations -template <> -bool LessThan(const BytesValue& t1, const BytesValue& t2) { - return t1.Compare(t2) < 0; -} - -template <> -bool LessThanOrEqual(const BytesValue& t1, const BytesValue& t2) { - return t1.Compare(t2) <= 0; -} - -template <> -bool GreaterThan(const BytesValue& t1, const BytesValue& t2) { - return t1.Compare(t2) > 0; -} - -template <> -bool GreaterThanOrEqual(const BytesValue& t1, const BytesValue& t2) { - return t1.Compare(t2) >= 0; -} - -// Duration comparison specializations -template <> -bool LessThan(absl::Duration t1, absl::Duration t2) { - return absl::operator<(t1, t2); -} - -template <> -bool LessThanOrEqual(absl::Duration t1, absl::Duration t2) { - return absl::operator<=(t1, t2); -} - -template <> -bool GreaterThan(absl::Duration t1, absl::Duration t2) { - return absl::operator>(t1, t2); -} - -template <> -bool GreaterThanOrEqual(absl::Duration t1, absl::Duration t2) { - return absl::operator>=(t1, t2); -} - -// Timestamp comparison specializations -template <> -bool LessThan(absl::Time t1, absl::Time t2) { - return absl::operator<(t1, t2); -} - -template <> -bool LessThanOrEqual(absl::Time t1, absl::Time t2) { - return absl::operator<=(t1, t2); -} - -template <> -bool GreaterThan(absl::Time t1, absl::Time t2) { - return absl::operator>(t1, t2); -} - -template <> -bool GreaterThanOrEqual(absl::Time t1, absl::Time t2) { - return absl::operator>=(t1, t2); -} - -template -bool CrossNumericLessThan(T t, U u) { - return Number(t) < Number(u); -} - -template -bool CrossNumericGreaterThan(T t, U u) { - return Number(t) > Number(u); -} - -template -bool CrossNumericLessOrEqualTo(T t, U u) { - return Number(t) <= Number(u); -} - -template -bool CrossNumericGreaterOrEqualTo(T t, U u) { - return Number(t) >= Number(u); +absl::optional NumberFromValue(const Value& value) { + switch (value.kind()) { + case ValueKind::kInt: + return Number::FromInt64(value.GetInt().NativeValue()); + case ValueKind::kUint: + return Number::FromUint64(value.GetUint().NativeValue()); + case ValueKind::kDouble: + return Number::FromDouble(value.GetDouble().NativeValue()); + default: + return absl::nullopt; + } } -template -absl::Status RegisterComparisonFunctionsForType( - cel::FunctionRegistry& registry) { - using FunctionAdapter = BinaryFunctionAdapter; - CEL_RETURN_IF_ERROR(registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kLess, false), - FunctionAdapter::WrapFunction(LessThan))); - - CEL_RETURN_IF_ERROR(registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kLessOrEqual, false), - FunctionAdapter::WrapFunction(LessThanOrEqual))); +class ComparisonFunction final : public Function { + public: + enum class Op { kLess, kLessOrEqual, kGreater, kGreaterOrEqual }; + + explicit ComparisonFunction(Op op) : op_(op) {} + + absl::StatusOr Invoke(absl::Span args, + const InvokeContext&) const override { + if (args.size() != 2) { + return absl::InvalidArgumentError( + "unexpected number of arguments for binary function"); + } + if (args[0].kind() == args[1].kind()) { + switch (args[0].kind()) { + case ValueKind::kBool: + return BoolValue(CompareOrdered(args[0].GetBool().NativeValue(), + args[1].GetBool().NativeValue())); + case ValueKind::kInt: + return BoolValue(CompareOrdered(args[0].GetInt().NativeValue(), + args[1].GetInt().NativeValue())); + case ValueKind::kUint: + return BoolValue(CompareOrdered(args[0].GetUint().NativeValue(), + args[1].GetUint().NativeValue())); + case ValueKind::kDouble: + return BoolValue(CompareOrdered(args[0].GetDouble().NativeValue(), + args[1].GetDouble().NativeValue())); + case ValueKind::kString: { + int cmp = args[0].GetString().Compare(args[1].GetString()); + return BoolValue(CompareThreeWay(cmp)); + } + case ValueKind::kBytes: { + int cmp = args[0].GetBytes().Compare(args[1].GetBytes()); + return BoolValue(CompareThreeWay(cmp)); + } + case ValueKind::kDuration: + return BoolValue(CompareOrdered(args[0].GetDuration().ToDuration(), + args[1].GetDuration().ToDuration())); + case ValueKind::kTimestamp: + return BoolValue(CompareOrdered(args[0].GetTimestamp().ToTime(), + args[1].GetTimestamp().ToTime())); + default: + return absl::InvalidArgumentError("expected comparable value"); + } + } + + absl::optional lhs = NumberFromValue(args[0]); + absl::optional rhs = NumberFromValue(args[1]); + if (!lhs.has_value() || !rhs.has_value()) { + return absl::InvalidArgumentError("expected numeric value"); + } + return BoolValue(CompareOrdered(*lhs, *rhs)); + } - CEL_RETURN_IF_ERROR(registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kGreater, false), - FunctionAdapter::WrapFunction(GreaterThan))); + private: + template + bool CompareOrdered(const T& lhs, const T& rhs) const { + switch (op_) { + case Op::kLess: + return lhs < rhs; + case Op::kLessOrEqual: + return lhs <= rhs; + case Op::kGreater: + return lhs > rhs; + case Op::kGreaterOrEqual: + return lhs >= rhs; + } + } - CEL_RETURN_IF_ERROR(registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kGreaterOrEqual, false), - FunctionAdapter::WrapFunction(GreaterThanOrEqual))); + bool CompareThreeWay(int cmp) const { + switch (op_) { + case Op::kLess: + return cmp < 0; + case Op::kLessOrEqual: + return cmp <= 0; + case Op::kGreater: + return cmp > 0; + case Op::kGreaterOrEqual: + return cmp >= 0; + } + } + Op op_; +}; + +absl::Status RegisterComparisonOverloads(FunctionRegistry& registry, Kind lhs, + Kind rhs) { + static constexpr struct { + absl::string_view name; + ComparisonFunction::Op op; + } kOps[] = { + {cel::builtin::kLess, ComparisonFunction::Op::kLess}, + {cel::builtin::kLessOrEqual, ComparisonFunction::Op::kLessOrEqual}, + {cel::builtin::kGreater, ComparisonFunction::Op::kGreater}, + {cel::builtin::kGreaterOrEqual, ComparisonFunction::Op::kGreaterOrEqual}, + }; + for (const auto& entry : kOps) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(entry.name, /*is_receiver_style=*/false, {lhs, rhs}), + std::make_unique(entry.op))); + } return absl::OkStatus(); } absl::Status RegisterHomogenousComparisonFunctions( cel::FunctionRegistry& registry) { - CEL_RETURN_IF_ERROR(RegisterComparisonFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR(RegisterComparisonFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR(RegisterComparisonFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR(RegisterComparisonFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR( - RegisterComparisonFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR( - RegisterComparisonFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR( - RegisterComparisonFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR(RegisterComparisonFunctionsForType(registry)); - - return absl::OkStatus(); -} - -template -absl::Status RegisterCrossNumericComparisons(cel::FunctionRegistry& registry) { - using FunctionAdapter = BinaryFunctionAdapter; - CEL_RETURN_IF_ERROR(registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kLess, - /*receiver_style=*/false), - FunctionAdapter::WrapFunction(&CrossNumericLessThan))); - CEL_RETURN_IF_ERROR(registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kGreater, - /*receiver_style=*/false), - FunctionAdapter::WrapFunction(&CrossNumericGreaterThan))); - CEL_RETURN_IF_ERROR(registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kGreaterOrEqual, - /*receiver_style=*/false), - FunctionAdapter::WrapFunction(&CrossNumericGreaterOrEqualTo))); - CEL_RETURN_IF_ERROR(registry.Register( - FunctionAdapter::CreateDescriptor(cel::builtin::kLessOrEqual, - /*receiver_style=*/false), - FunctionAdapter::WrapFunction(&CrossNumericLessOrEqualTo))); + static constexpr Kind kOrderableKinds[] = { + Kind::kBool, Kind::kInt, Kind::kUint, Kind::kDouble, + Kind::kString, Kind::kBytes, Kind::kDuration, Kind::kTimestamp, + }; + for (Kind kind : kOrderableKinds) { + CEL_RETURN_IF_ERROR(RegisterComparisonOverloads(registry, kind, kind)); + } return absl::OkStatus(); } absl::Status RegisterHeterogeneousComparisonFunctions( cel::FunctionRegistry& registry) { - CEL_RETURN_IF_ERROR( - (RegisterCrossNumericComparisons(registry))); - CEL_RETURN_IF_ERROR( - (RegisterCrossNumericComparisons(registry))); - - CEL_RETURN_IF_ERROR( - (RegisterCrossNumericComparisons(registry))); - CEL_RETURN_IF_ERROR( - (RegisterCrossNumericComparisons(registry))); - - CEL_RETURN_IF_ERROR( - (RegisterCrossNumericComparisons(registry))); - CEL_RETURN_IF_ERROR( - (RegisterCrossNumericComparisons(registry))); - - CEL_RETURN_IF_ERROR(RegisterComparisonFunctionsForType(registry)); - CEL_RETURN_IF_ERROR(RegisterComparisonFunctionsForType(registry)); - CEL_RETURN_IF_ERROR(RegisterComparisonFunctionsForType(registry)); - CEL_RETURN_IF_ERROR(RegisterComparisonFunctionsForType(registry)); - CEL_RETURN_IF_ERROR( - RegisterComparisonFunctionsForType(registry)); - CEL_RETURN_IF_ERROR( - RegisterComparisonFunctionsForType(registry)); - CEL_RETURN_IF_ERROR( - RegisterComparisonFunctionsForType(registry)); - CEL_RETURN_IF_ERROR(RegisterComparisonFunctionsForType(registry)); - - return absl::OkStatus(); + static constexpr std::pair kCrossNumericKinds[] = { + {Kind::kDouble, Kind::kInt}, {Kind::kDouble, Kind::kUint}, + {Kind::kUint, Kind::kDouble}, {Kind::kUint, Kind::kInt}, + {Kind::kInt, Kind::kDouble}, {Kind::kInt, Kind::kUint}, + }; + for (const auto& pair : kCrossNumericKinds) { + CEL_RETURN_IF_ERROR( + RegisterComparisonOverloads(registry, pair.first, pair.second)); + } + return RegisterHomogenousComparisonFunctions(registry); } + } // namespace absl::Status RegisterComparisonFunctions(FunctionRegistry& registry, diff --git a/runtime/standard/container_functions.cc b/runtime/standard/container_functions.cc index c81dc7596..85a8073c2 100644 --- a/runtime/standard/container_functions.cc +++ b/runtime/standard/container_functions.cc @@ -16,16 +16,21 @@ #include #include +#include #include #include "absl/base/nullability.h" #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "absl/types/span.h" #include "base/builtins.h" -#include "base/function_adapter.h" +#include "common/function_descriptor.h" +#include "common/kind.h" #include "common/value.h" +#include "common/value_kind.h" #include "common/values/list_value_builder.h" #include "internal/status_macros.h" +#include "runtime/function.h" #include "runtime/function_registry.h" #include "runtime/runtime_options.h" #include "google/protobuf/arena.h" @@ -35,14 +40,6 @@ namespace cel { namespace { -absl::StatusOr MapSizeImpl(const MapValue& value) { - return value.Size(); -} - -absl::StatusOr ListSizeImpl(const ListValue& value) { - return value.Size(); -} - // Concatenation for CelList type. absl::StatusOr ConcatList( const ListValue& value1, const ListValue& value2, @@ -95,6 +92,64 @@ absl::StatusOr AppendList(ListValue value1, const Value& value2) { } return absl::InvalidArgumentError("Unexpected call to runtime list append."); } + +class ContainerFunction final : public Function { + public: + enum class Op { kSize, kConcatList, kAppendList }; + + explicit ContainerFunction(Op op) : op_(op) {} + + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + switch (op_) { + case Op::kSize: { + if (args.size() != 1) { + return absl::InvalidArgumentError( + "unexpected number of arguments for unary function"); + } + absl::StatusOr size; + switch (args[0].kind()) { + case ValueKind::kList: + size = args[0].GetList().Size(); + break; + case ValueKind::kMap: + size = args[0].GetMap().Size(); + break; + default: + return absl::InvalidArgumentError("expected list or map value"); + } + if (!size.ok()) { + return std::move(size).status(); + } + return IntValue(*size); + } + case Op::kConcatList: + if (args.size() != 2) { + return absl::InvalidArgumentError( + "unexpected number of arguments for binary function"); + } + if (!args[0].IsList() || !args[1].IsList()) { + return absl::InvalidArgumentError("expected list value"); + } + return ConcatList(args[0].GetList(), args[1].GetList(), + context.descriptor_pool(), context.message_factory(), + context.arena()); + case Op::kAppendList: + if (args.size() != 2) { + return absl::InvalidArgumentError( + "unexpected number of arguments for binary function"); + } + if (!args[0].IsList()) { + return absl::InvalidArgumentError("expected list value"); + } + return AppendList(args[0].GetList(), args[1]); + } + } + + private: + Op op_; +}; + } // namespace absl::Status RegisterContainerFunctions(FunctionRegistry& registry, @@ -102,35 +157,26 @@ absl::Status RegisterContainerFunctions(FunctionRegistry& registry, // receiver style = true/false // Support both the global and receiver style size() for lists and maps. for (bool receiver_style : {true, false}) { - CEL_RETURN_IF_ERROR(registry.Register( - cel::UnaryFunctionAdapter, const ListValue&>:: - CreateDescriptor(cel::builtin::kSize, receiver_style), - UnaryFunctionAdapter, - const ListValue&>::WrapFunction(ListSizeImpl))); - - CEL_RETURN_IF_ERROR(registry.Register( - UnaryFunctionAdapter, const MapValue&>:: - CreateDescriptor(cel::builtin::kSize, receiver_style), - UnaryFunctionAdapter, - const MapValue&>::WrapFunction(MapSizeImpl))); + for (Kind kind : {Kind::kList, Kind::kMap}) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(cel::builtin::kSize, receiver_style, {kind}), + std::make_unique(ContainerFunction::Op::kSize))); + } } if (options.enable_list_concat) { CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter< - absl::StatusOr, const ListValue&, - const ListValue&>::CreateDescriptor(cel::builtin::kAdd, false), - BinaryFunctionAdapter, const ListValue&, - const ListValue&>::WrapFunction(ConcatList))); + FunctionDescriptor(cel::builtin::kAdd, /*is_receiver_style=*/false, + {Kind::kList, Kind::kList}), + std::make_unique( + ContainerFunction::Op::kConcatList))); } return registry.Register( - BinaryFunctionAdapter< - absl::StatusOr, ListValue, - const Value&>::CreateDescriptor(cel::builtin::kRuntimeListAppend, - false), - BinaryFunctionAdapter, ListValue, - const Value&>::WrapFunction(AppendList)); + FunctionDescriptor(cel::builtin::kRuntimeListAppend, + /*is_receiver_style=*/false, + {Kind::kList, Kind::kAny}), + std::make_unique(ContainerFunction::Op::kAppendList)); } } // namespace cel diff --git a/runtime/standard/container_membership_functions.cc b/runtime/standard/container_membership_functions.cc index 26c115103..eda86f154 100644 --- a/runtime/standard/container_membership_functions.cc +++ b/runtime/standard/container_membership_functions.cc @@ -16,22 +16,22 @@ #include #include +#include -#include "absl/base/nullability.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "base/builtins.h" -#include "base/function_adapter.h" +#include "common/function_descriptor.h" +#include "common/kind.h" #include "common/value.h" +#include "common/value_kind.h" #include "internal/number.h" #include "internal/status_macros.h" +#include "runtime/function.h" #include "runtime/function_registry.h" -#include "runtime/register_function_helper.h" #include "runtime/runtime_options.h" -#include "google/protobuf/arena.h" -#include "google/protobuf/descriptor.h" -#include "google/protobuf/message.h" namespace cel { namespace { @@ -44,274 +44,240 @@ static constexpr std::array in_operators = { cel::builtin::kInDeprecated, // deprecated _in_ -- for backwards compat }; -template -bool ValueEquals(const Value& value, T other); - -template <> -bool ValueEquals(const Value& value, bool other) { - if (auto bool_value = value.As(); bool_value) { - return bool_value->NativeValue() == other; - } - return false; -} - -template <> -bool ValueEquals(const Value& value, int64_t other) { - if (auto int_value = value.As(); int_value) { - return int_value->NativeValue() == other; +bool HomogenousValueEquals(const Value& lhs, const Value& rhs) { + if (lhs.kind() != rhs.kind()) { + return false; } - return false; -} - -template <> -bool ValueEquals(const Value& value, uint64_t other) { - if (auto uint_value = value.As(); uint_value) { - return uint_value->NativeValue() == other; + switch (lhs.kind()) { + case ValueKind::kBool: + return lhs.GetBool().NativeValue() == rhs.GetBool().NativeValue(); + case ValueKind::kInt: + return lhs.GetInt().NativeValue() == rhs.GetInt().NativeValue(); + case ValueKind::kUint: + return lhs.GetUint().NativeValue() == rhs.GetUint().NativeValue(); + case ValueKind::kDouble: + return lhs.GetDouble().NativeValue() == rhs.GetDouble().NativeValue(); + case ValueKind::kString: + return lhs.GetString().Equals(rhs.GetString()); + case ValueKind::kBytes: + return lhs.GetBytes().Equals(rhs.GetBytes()); + default: + return false; } - return false; } -template <> -bool ValueEquals(const Value& value, double other) { - if (auto double_value = value.As(); double_value) { - return double_value->NativeValue() == other; +absl::StatusOr EvaluateHomogenousListIn( + const Value& key, const ListValue& list, + const Function::InvokeContext& context) { + switch (key.kind()) { + case ValueKind::kBool: + case ValueKind::kInt: + case ValueKind::kUint: + case ValueKind::kDouble: + case ValueKind::kString: + case ValueKind::kBytes: + break; + default: + return absl::InvalidArgumentError("unsupported key kind for list in"); } - return false; -} - -template <> -bool ValueEquals(const Value& value, const StringValue& other) { - if (auto string_value = value.As(); string_value) { - return string_value->Equals(other); - } - return false; -} - -template <> -bool ValueEquals(const Value& value, const BytesValue& other) { - if (auto bytes_value = value.As(); bytes_value) { - return bytes_value->Equals(other); - } - return false; -} - -// Template function implementing CEL in() function -template -absl::StatusOr In( - T value, const ListValue& list, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) { CEL_ASSIGN_OR_RETURN(auto size, list.Size()); Value element; for (int i = 0; i < size; i++) { - CEL_RETURN_IF_ERROR( - list.Get(i, descriptor_pool, message_factory, arena, &element)); - if (ValueEquals(element, value)) { - return true; + CEL_RETURN_IF_ERROR(list.Get(i, context.descriptor_pool(), + context.message_factory(), context.arena(), + &element)); + if (HomogenousValueEquals(element, key)) { + return BoolValue(true); } } - - return false; + return BoolValue(false); } -// Implementation for @in operator using heterogeneous equality. -absl::StatusOr HeterogeneousEqualityIn( - const Value& value, const ListValue& list, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) { - return list.Contains(value, descriptor_pool, message_factory, arena); -} - -absl::Status RegisterListMembershipFunctions(FunctionRegistry& registry, - const RuntimeOptions& options) { - for (absl::string_view op : in_operators) { - if (options.enable_heterogeneous_equality) { - CEL_RETURN_IF_ERROR( - (RegisterHelper, const Value&, const ListValue&>>:: - RegisterGlobalOverload(op, &HeterogeneousEqualityIn, registry))); - } else { - CEL_RETURN_IF_ERROR( - (RegisterHelper, bool, - const ListValue&>>:: - RegisterGlobalOverload(op, In, registry))); - CEL_RETURN_IF_ERROR( - (RegisterHelper, int64_t, - const ListValue&>>:: - RegisterGlobalOverload(op, In, registry))); - CEL_RETURN_IF_ERROR( - (RegisterHelper, uint64_t, - const ListValue&>>:: - RegisterGlobalOverload(op, In, registry))); - CEL_RETURN_IF_ERROR( - (RegisterHelper, double, - const ListValue&>>:: - RegisterGlobalOverload(op, In, registry))); - CEL_RETURN_IF_ERROR( - (RegisterHelper, const StringValue&, const ListValue&>>:: - RegisterGlobalOverload(op, In, registry))); - CEL_RETURN_IF_ERROR( - (RegisterHelper, const BytesValue&, const ListValue&>>:: - RegisterGlobalOverload(op, In, registry))); +absl::StatusOr EvaluateMapIn(const Value& key, const MapValue& map_value, + bool enable_heterogeneous_equality, + const Function::InvokeContext& context) { + switch (key.kind()) { + case ValueKind::kString: { + Value result; + CEL_RETURN_IF_ERROR(map_value.Has(key, context.descriptor_pool(), + context.message_factory(), + context.arena(), &result)); + if (result.IsBool()) { + return result; + } + if (enable_heterogeneous_equality) { + return BoolValue(false); + } + return result; } - } - return absl::OkStatus(); -} - -absl::Status RegisterMapMembershipFunctions(FunctionRegistry& registry, - const RuntimeOptions& options) { - const bool enable_heterogeneous_equality = - options.enable_heterogeneous_equality; - - auto boolKeyInSet = - [enable_heterogeneous_equality]( - bool key, const MapValue& map_value, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) -> absl::StatusOr { - Value has; - CEL_RETURN_IF_ERROR(map_value.Has(BoolValue(key), descriptor_pool, - message_factory, arena, &has)); - if (has.IsTrue()) { + case ValueKind::kBool: { + Value has; + CEL_RETURN_IF_ERROR(map_value.Has(key, context.descriptor_pool(), + context.message_factory(), + context.arena(), &has)); + if (has.IsTrue()) { + return has; + } + if (enable_heterogeneous_equality) { + return BoolValue(false); + } return has; } - if (enable_heterogeneous_equality) { - return BoolValue(false); - } - return has; - }; - - auto intKeyInSet = - [enable_heterogeneous_equality]( - int64_t key, const MapValue& map_value, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) -> absl::StatusOr { - Value result; - CEL_RETURN_IF_ERROR(map_value.Has(IntValue(key), descriptor_pool, - message_factory, arena, &result)); - if (enable_heterogeneous_equality) { - if (result.IsTrue()) { - return result; - } - Number number = Number::FromInt64(key); - if (number.LosslessConvertibleToUint()) { - Value result_alt; - CEL_RETURN_IF_ERROR(map_value.Has(UintValue(number.AsUint()), - descriptor_pool, message_factory, - arena, &result_alt)); - if (result_alt.IsTrue()) { - return result_alt; + case ValueKind::kInt: { + Value result; + CEL_RETURN_IF_ERROR(map_value.Has(key, context.descriptor_pool(), + context.message_factory(), + context.arena(), &result)); + if (enable_heterogeneous_equality) { + if (result.IsTrue()) { + return result; } + Number number = Number::FromInt64(key.GetInt().NativeValue()); + if (number.LosslessConvertibleToUint()) { + Value result_alt; + CEL_RETURN_IF_ERROR(map_value.Has( + UintValue(number.AsUint()), context.descriptor_pool(), + context.message_factory(), context.arena(), &result_alt)); + if (result_alt.IsTrue()) { + return result_alt; + } + } + return BoolValue(false); } - return BoolValue(false); - } - return result; - }; - - auto stringKeyInSet = - [enable_heterogeneous_equality]( - const StringValue& key, const MapValue& map_value, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) -> absl::StatusOr { - Value result; - CEL_RETURN_IF_ERROR( - map_value.Has(key, descriptor_pool, message_factory, arena, &result)); - if (result.IsBool()) { return result; } - if (enable_heterogeneous_equality) { - return BoolValue(false); + case ValueKind::kUint: { + Value has; + CEL_RETURN_IF_ERROR(map_value.Has(key, context.descriptor_pool(), + context.message_factory(), + context.arena(), &has)); + if (enable_heterogeneous_equality) { + if (has.IsTrue()) { + return has; + } + Number number = Number::FromUint64(key.GetUint().NativeValue()); + if (number.LosslessConvertibleToInt()) { + Value has_alt; + CEL_RETURN_IF_ERROR(map_value.Has( + IntValue(number.AsInt()), context.descriptor_pool(), + context.message_factory(), context.arena(), &has_alt)); + if (has_alt.IsTrue()) { + return has_alt; + } + } + return BoolValue(false); + } + return has; } - return result; - }; - - auto uintKeyInSet = - [enable_heterogeneous_equality]( - uint64_t key, const MapValue& map_value, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) -> absl::StatusOr { - Value has; - CEL_RETURN_IF_ERROR(map_value.Has(UintValue(key), descriptor_pool, - message_factory, arena, &has)); - if (enable_heterogeneous_equality) { - if (has.IsTrue()) { - return has; + case ValueKind::kDouble: { + if (!enable_heterogeneous_equality) { + return absl::InvalidArgumentError( + "double key not supported for homogenous map in"); } - Value has_alt; - Number number = Number::FromUint64(key); + Number number = Number::FromDouble(key.GetDouble().NativeValue()); if (number.LosslessConvertibleToInt()) { - CEL_RETURN_IF_ERROR(map_value.Has(IntValue(number.AsInt()), - descriptor_pool, message_factory, - arena, &has_alt)); + Value has; + CEL_RETURN_IF_ERROR( + map_value.Has(IntValue(number.AsInt()), context.descriptor_pool(), + context.message_factory(), context.arena(), &has)); + if (has.IsTrue()) { + return has; + } + } + if (number.LosslessConvertibleToUint()) { + Value has; + CEL_RETURN_IF_ERROR( + map_value.Has(UintValue(number.AsUint()), context.descriptor_pool(), + context.message_factory(), context.arena(), &has)); if (has.IsTrue()) { return has; } } return BoolValue(false); } - return has; - }; + default: + return absl::InvalidArgumentError("unsupported key kind for map in"); + } +} - auto doubleKeyInSet = - [](double key, const MapValue& map_value, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) -> absl::StatusOr { - Number number = Number::FromDouble(key); - if (number.LosslessConvertibleToInt()) { - Value has; - CEL_RETURN_IF_ERROR(map_value.Has(IntValue(number.AsInt()), - descriptor_pool, message_factory, arena, - &has)); - if (has.IsTrue()) { - return has; - } +class ContainerMembershipFunction final : public Function { + public: + explicit ContainerMembershipFunction(bool enable_heterogeneous_equality) + : enable_heterogeneous_equality_(enable_heterogeneous_equality) {} + + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + if (args.size() != 2) { + return absl::InvalidArgumentError( + "unexpected number of arguments for binary function"); } - if (number.LosslessConvertibleToUint()) { - Value has; - CEL_RETURN_IF_ERROR(map_value.Has(UintValue(number.AsUint()), - descriptor_pool, message_factory, arena, - &has)); - if (has.IsTrue()) { - return has; - } + switch (args[1].kind()) { + case ValueKind::kList: + if (enable_heterogeneous_equality_) { + return args[1].GetList().Contains(args[0], context.descriptor_pool(), + context.message_factory(), + context.arena()); + } + return EvaluateHomogenousListIn(args[0], args[1].GetList(), context); + case ValueKind::kMap: + return EvaluateMapIn(args[0], args[1].GetMap(), + enable_heterogeneous_equality_, context); + default: + return absl::InvalidArgumentError("expected list or map value"); } - return BoolValue(false); - }; - - for (auto op : in_operators) { - auto status = RegisterHelper, const StringValue&, - const MapValue&>>::RegisterGlobalOverload(op, stringKeyInSet, registry); - if (!status.ok()) return status; - - status = RegisterHelper< - BinaryFunctionAdapter, bool, const MapValue&>>:: - RegisterGlobalOverload(op, boolKeyInSet, registry); - if (!status.ok()) return status; + } - status = RegisterHelper, - int64_t, const MapValue&>>:: - RegisterGlobalOverload(op, intKeyInSet, registry); - if (!status.ok()) return status; + private: + bool enable_heterogeneous_equality_; +}; - status = RegisterHelper, - uint64_t, const MapValue&>>:: - RegisterGlobalOverload(op, uintKeyInSet, registry); - if (!status.ok()) return status; +absl::Status RegisterListMembershipFunctions(FunctionRegistry& registry, + const RuntimeOptions& options) { + static constexpr Kind kHomogenousListKeyKinds[] = { + Kind::kBool, Kind::kInt, Kind::kUint, + Kind::kDouble, Kind::kString, Kind::kBytes, + }; + for (absl::string_view op : in_operators) { + if (options.enable_heterogeneous_equality) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(op, /*is_receiver_style=*/false, + {Kind::kAny, Kind::kList}), + std::make_unique(true))); + } else { + for (Kind kind : kHomogenousListKeyKinds) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(op, /*is_receiver_style=*/false, + {kind, Kind::kList}), + std::make_unique(false))); + } + } + } + return absl::OkStatus(); +} +absl::Status RegisterMapMembershipFunctions(FunctionRegistry& registry, + const RuntimeOptions& options) { + const bool enable_heterogeneous_equality = + options.enable_heterogeneous_equality; + static constexpr Kind kMapKeyKinds[] = { + Kind::kString, + Kind::kBool, + Kind::kInt, + Kind::kUint, + }; + for (absl::string_view op : in_operators) { + for (Kind kind : kMapKeyKinds) { + CEL_RETURN_IF_ERROR( + registry.Register(FunctionDescriptor(op, /*is_receiver_style=*/false, + {kind, Kind::kMap}), + std::make_unique( + enable_heterogeneous_equality))); + } if (enable_heterogeneous_equality) { - status = RegisterHelper, - double, const MapValue&>>:: - RegisterGlobalOverload(op, doubleKeyInSet, registry); - if (!status.ok()) return status; + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(op, /*is_receiver_style=*/false, + {Kind::kDouble, Kind::kMap}), + std::make_unique(true))); } } return absl::OkStatus(); diff --git a/runtime/standard/equality_functions.cc b/runtime/standard/equality_functions.cc index 03e29223f..0a9dc72b9 100644 --- a/runtime/standard/equality_functions.cc +++ b/runtime/standard/equality_functions.cc @@ -15,27 +15,26 @@ #include "runtime/standard/equality_functions.h" #include -#include -#include -#include +#include #include #include "absl/base/nullability.h" -#include "absl/functional/function_ref.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "absl/types/optional.h" +#include "absl/types/span.h" #include "base/builtins.h" -#include "base/function_adapter.h" +#include "common/function_descriptor.h" +#include "common/kind.h" #include "common/value.h" #include "common/value_kind.h" #include "internal/number.h" #include "internal/status_macros.h" +#include "runtime/function.h" #include "runtime/function_registry.h" #include "runtime/internal/errors.h" -#include "runtime/register_function_helper.h" #include "runtime/runtime_options.h" #include "google/protobuf/arena.h" #include "google/protobuf/descriptor.h" @@ -72,57 +71,6 @@ struct HeterogeneousEqualProvider { google::protobuf::Arena* absl_nonnull arena) const; }; -// Comparison template functions -template -absl::optional Inequal(Type lhs, Type rhs) { - return lhs != rhs; -} - -template <> -absl::optional Inequal(const StringValue& lhs, const StringValue& rhs) { - return !lhs.Equals(rhs); -} - -template <> -absl::optional Inequal(const BytesValue& lhs, const BytesValue& rhs) { - return !lhs.Equals(rhs); -} - -template <> -absl::optional Inequal(const NullValue&, const NullValue&) { - return false; -} - -template <> -absl::optional Inequal(const TypeValue& lhs, const TypeValue& rhs) { - return lhs.name() != rhs.name(); -} - -template -absl::optional Equal(Type lhs, Type rhs) { - return lhs == rhs; -} - -template <> -absl::optional Equal(const StringValue& lhs, const StringValue& rhs) { - return lhs.Equals(rhs); -} - -template <> -absl::optional Equal(const BytesValue& lhs, const BytesValue& rhs) { - return lhs.Equals(rhs); -} - -template <> -absl::optional Equal(const NullValue&, const NullValue&) { - return true; -} - -template <> -absl::optional Equal(const TypeValue& lhs, const TypeValue& rhs) { - return lhs.name() == rhs.name(); -} - // Equality for lists. Template parameter provides either heterogeneous or // homogenous equality for comparing members. template @@ -181,7 +129,7 @@ absl::optional NumberFromValue(const Value& value) { return Number::FromDouble(value.GetDouble().NativeValue()); } - return std::nullopt; + return absl::nullopt; } absl::StatusOr> CheckAlternativeNumericType( @@ -192,7 +140,7 @@ absl::StatusOr> CheckAlternativeNumericType( absl::optional number = NumberFromValue(key); if (!number.has_value()) { - return std::nullopt; + return absl::nullopt; } if (!key.IsInt() && number->LosslessConvertibleToInt()) { @@ -215,7 +163,7 @@ absl::StatusOr> CheckAlternativeNumericType( } } - return std::nullopt; + return absl::nullopt; } // Equality for maps. Template parameter provides either heterogeneous or @@ -266,174 +214,6 @@ absl::StatusOr> MapEqual( return true; } -// Helper for wrapping ==/!= implementations. -// Name should point to a static constexpr string so the lambda capture is safe. -template -std::function -WrapComparison(Op op, absl::string_view name) { - return [op = std::move(op), name]( - Type lhs, Type rhs, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) -> Value { - absl::optional result = op(lhs, rhs); - - if (result.has_value()) { - return BoolValue(*result); - } - - return ErrorValue::From( - cel::runtime_internal::CreateNoMatchingOverloadError(name), arena); - }; -} - -// Helper method -// -// Registers all equality functions for template parameters type. -template -absl::Status RegisterEqualityFunctionsForType(cel::FunctionRegistry& registry) { - using FunctionAdapter = - cel::RegisterHelper>; - // Inequality - CEL_RETURN_IF_ERROR(FunctionAdapter::RegisterGlobalOverload( - kInequal, WrapComparison(&Inequal, kInequal), registry)); - - // Equality - CEL_RETURN_IF_ERROR(FunctionAdapter::RegisterGlobalOverload( - kEqual, WrapComparison(&Equal, kEqual), registry)); - - return absl::OkStatus(); -} - -template -auto ComplexEquality(Op&& op) { - return [op = std::forward(op)]( - const Type& t1, const Type& t2, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) -> absl::StatusOr { - CEL_ASSIGN_OR_RETURN(absl::optional result, - op(t1, t2, descriptor_pool, message_factory, arena)); - if (!result.has_value()) { - return ErrorValue::From( - cel::runtime_internal::CreateNoMatchingOverloadError(kEqual), arena); - } - return BoolValue(*result); - }; -} - -template -auto ComplexInequality(Op&& op) { - return [op = std::forward(op)]( - Type t1, Type t2, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) -> absl::StatusOr { - CEL_ASSIGN_OR_RETURN(absl::optional result, - op(t1, t2, descriptor_pool, message_factory, arena)); - if (!result.has_value()) { - return ErrorValue::From( - cel::runtime_internal::CreateNoMatchingOverloadError(kInequal), - arena); - } - return BoolValue(!*result); - }; -} - -template -absl::Status RegisterComplexEqualityFunctionsForType( - absl::FunctionRef>( - Type, Type, const google::protobuf::DescriptorPool* absl_nonnull, - google::protobuf::MessageFactory* absl_nonnull, google::protobuf::Arena* absl_nonnull)> - op, - cel::FunctionRegistry& registry) { - using FunctionAdapter = cel::RegisterHelper< - BinaryFunctionAdapter, Type, Type>>; - // Inequality - CEL_RETURN_IF_ERROR(FunctionAdapter::RegisterGlobalOverload( - kInequal, ComplexInequality(op), registry)); - - // Equality - CEL_RETURN_IF_ERROR(FunctionAdapter::RegisterGlobalOverload( - kEqual, ComplexEquality(op), registry)); - - return absl::OkStatus(); -} - -absl::Status RegisterHomogenousEqualityFunctions( - cel::FunctionRegistry& registry) { - CEL_RETURN_IF_ERROR(RegisterEqualityFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR(RegisterEqualityFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR(RegisterEqualityFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR(RegisterEqualityFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR( - RegisterEqualityFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR( - RegisterEqualityFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR( - RegisterEqualityFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR(RegisterEqualityFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR( - RegisterEqualityFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR( - RegisterEqualityFunctionsForType(registry)); - - CEL_RETURN_IF_ERROR( - RegisterComplexEqualityFunctionsForType( - &ListEqual, registry)); - - CEL_RETURN_IF_ERROR( - RegisterComplexEqualityFunctionsForType( - &MapEqual, registry)); - - return absl::OkStatus(); -} - -absl::Status RegisterNullMessageEqualityFunctions(FunctionRegistry& registry) { - // equals - CEL_RETURN_IF_ERROR( - (cel::RegisterHelper< - BinaryFunctionAdapter>:: - RegisterGlobalOverload( - kEqual, - [](const StructValue&, const NullValue&) { return false; }, - registry))); - - CEL_RETURN_IF_ERROR( - (cel::RegisterHelper< - BinaryFunctionAdapter>:: - RegisterGlobalOverload( - kEqual, - [](const NullValue&, const StructValue&) { return false; }, - registry))); - - // inequals - CEL_RETURN_IF_ERROR( - (cel::RegisterHelper< - BinaryFunctionAdapter>:: - RegisterGlobalOverload( - kInequal, - [](const StructValue&, const NullValue&) { return true; }, - registry))); - - return cel::RegisterHelper< - BinaryFunctionAdapter>:: - RegisterGlobalOverload( - kInequal, [](const NullValue&, const StructValue&) { return true; }, - registry); -} - template absl::StatusOr> HomogenousValueEqual( const Value& v1, const Value& v2, @@ -441,39 +221,33 @@ absl::StatusOr> HomogenousValueEqual( google::protobuf::MessageFactory* absl_nonnull message_factory, google::protobuf::Arena* absl_nonnull arena) { if (v1.kind() != v2.kind()) { - return std::nullopt; + return absl::nullopt; } static_assert(std::is_lvalue_reference_v, "unexpected value copy"); - switch (v1->kind()) { + switch (v1.kind()) { case ValueKind::kBool: - return Equal(v1.GetBool().NativeValue(), - v2.GetBool().NativeValue()); + return v1.GetBool().NativeValue() == v2.GetBool().NativeValue(); case ValueKind::kNull: - return Equal(v1.GetNull(), v2.GetNull()); + return true; case ValueKind::kInt: - return Equal(v1.GetInt().NativeValue(), - v2.GetInt().NativeValue()); + return v1.GetInt().NativeValue() == v2.GetInt().NativeValue(); case ValueKind::kUint: - return Equal(v1.GetUint().NativeValue(), - v2.GetUint().NativeValue()); + return v1.GetUint().NativeValue() == v2.GetUint().NativeValue(); case ValueKind::kDouble: - return Equal(v1.GetDouble().NativeValue(), - v2.GetDouble().NativeValue()); + return v1.GetDouble().NativeValue() == v2.GetDouble().NativeValue(); case ValueKind::kDuration: - return Equal(v1.GetDuration().NativeValue(), - v2.GetDuration().NativeValue()); + return v1.GetDuration().ToDuration() == v2.GetDuration().ToDuration(); case ValueKind::kTimestamp: - return Equal(v1.GetTimestamp().NativeValue(), - v2.GetTimestamp().NativeValue()); + return v1.GetTimestamp().ToTime() == v2.GetTimestamp().ToTime(); case ValueKind::kCelType: - return Equal(v1.GetType(), v2.GetType()); + return v1.GetType().name() == v2.GetType().name(); case ValueKind::kString: - return Equal(v1.GetString(), v2.GetString()); + return v1.GetString().Equals(v2.GetString()); case ValueKind::kBytes: - return Equal(v1.GetBytes(), v2.GetBytes()); + return v1.GetBytes().Equals(v2.GetBytes()); case ValueKind::kList: return ListEqual(v1.GetList(), v2.GetList(), descriptor_pool, message_factory, arena); @@ -484,51 +258,108 @@ absl::StatusOr> HomogenousValueEqual( return OpaqueEqual(v1.GetOpaque(), v2.GetOpaque(), descriptor_pool, message_factory, arena); default: - return std::nullopt; + return absl::nullopt; } } -absl::StatusOr EqualOverloadImpl( - const Value& lhs, const Value& rhs, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) { - CEL_ASSIGN_OR_RETURN(absl::optional result, - runtime_internal::ValueEqualImpl( - lhs, rhs, descriptor_pool, message_factory, arena)); - if (result.has_value()) { - return BoolValue(*result); +class HomogenousEqualityFunction final : public Function { + public: + explicit HomogenousEqualityFunction(bool negation) : negation_(negation) {} + + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + if (args.size() != 2) { + return absl::InvalidArgumentError( + "unexpected number of arguments for binary function"); + } + if ((args[0].IsStruct() && args[1].IsNull()) || + (args[0].IsNull() && args[1].IsStruct())) { + return BoolValue(negation_); + } + CEL_ASSIGN_OR_RETURN(absl::optional result, + HomogenousValueEqual( + args[0], args[1], context.descriptor_pool(), + context.message_factory(), context.arena())); + if (!result.has_value()) { + return ErrorValue::From( + cel::runtime_internal::CreateNoMatchingOverloadError( + negation_ ? kInequal : kEqual), + context.arena()); + } + return BoolValue(negation_ ? !*result : *result); } - return ErrorValue::From( - cel::runtime_internal::CreateNoMatchingOverloadError(kEqual), arena); -} -absl::StatusOr InequalOverloadImpl( - const Value& lhs, const Value& rhs, - const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool, - google::protobuf::MessageFactory* absl_nonnull message_factory, - google::protobuf::Arena* absl_nonnull arena) { - CEL_ASSIGN_OR_RETURN(absl::optional result, - runtime_internal::ValueEqualImpl( - lhs, rhs, descriptor_pool, message_factory, arena)); - if (result.has_value()) { - return BoolValue(!*result); + private: + bool negation_; +}; + +class HeterogeneousEqualityFunction final : public Function { + public: + explicit HeterogeneousEqualityFunction(bool negation) : negation_(negation) {} + + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + if (args.size() != 2) { + return absl::InvalidArgumentError( + "unexpected number of arguments for binary function"); + } + CEL_ASSIGN_OR_RETURN(absl::optional result, + runtime_internal::ValueEqualImpl( + args[0], args[1], context.descriptor_pool(), + context.message_factory(), context.arena())); + if (!result.has_value()) { + return ErrorValue::From( + cel::runtime_internal::CreateNoMatchingOverloadError( + negation_ ? kInequal : kEqual), + context.arena()); + } + return BoolValue(negation_ ? !*result : *result); } - return ErrorValue::From( - cel::runtime_internal::CreateNoMatchingOverloadError(kInequal), arena); + + private: + bool negation_; +}; + +absl::Status RegisterHomogenousPair(FunctionRegistry& registry, Kind lhs, + Kind rhs) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(kInequal, /*is_receiver_style=*/false, {lhs, rhs}), + std::make_unique(/*negation=*/true))); + return registry.Register( + FunctionDescriptor(kEqual, /*is_receiver_style=*/false, {lhs, rhs}), + std::make_unique(/*negation=*/false)); } -absl::Status RegisterHeterogeneousEqualityFunctions( +absl::Status RegisterHomogenousEqualityFunctions( cel::FunctionRegistry& registry) { - using Adapter = cel::RegisterHelper< - BinaryFunctionAdapter, const Value&, const Value&>>; - CEL_RETURN_IF_ERROR( - Adapter::RegisterGlobalOverload(kEqual, &EqualOverloadImpl, registry)); + static constexpr Kind kKinds[] = { + Kind::kBool, Kind::kInt, Kind::kUint, Kind::kDouble, + Kind::kString, Kind::kBytes, Kind::kDuration, Kind::kTimestamp, + Kind::kNull, Kind::kType, Kind::kList, Kind::kMap, + }; + for (Kind kind : kKinds) { + CEL_RETURN_IF_ERROR(RegisterHomogenousPair(registry, kind, kind)); + } + return absl::OkStatus(); +} - CEL_RETURN_IF_ERROR(Adapter::RegisterGlobalOverload( - kInequal, &InequalOverloadImpl, registry)); +absl::Status RegisterNullMessageEqualityFunctions(FunctionRegistry& registry) { + CEL_RETURN_IF_ERROR( + RegisterHomogenousPair(registry, Kind::kStruct, Kind::kNull)); + return RegisterHomogenousPair(registry, Kind::kNull, Kind::kStruct); +} - return absl::OkStatus(); +absl::Status RegisterHeterogeneousEqualityFunctions( + cel::FunctionRegistry& registry) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(kEqual, /*is_receiver_style=*/false, + {Kind::kAny, Kind::kAny}), + std::make_unique(/*negation=*/false))); + + return registry.Register( + FunctionDescriptor(kInequal, /*is_receiver_style=*/false, + {Kind::kAny, Kind::kAny}), + std::make_unique(/*negation=*/true)); } absl::StatusOr> HomogenousEqualProvider::operator()( @@ -583,7 +414,7 @@ absl::StatusOr> ValueEqualImpl( // map containing an Error. Return no matching overload to propagate an error // instead of a false result. if (v1.IsError() || v1.IsUnknown() || v2.IsError() || v2.IsUnknown()) { - return std::nullopt; + return absl::nullopt; } return false; diff --git a/runtime/standard/logical_functions.cc b/runtime/standard/logical_functions.cc index 6eed8b695..ff2e09f91 100644 --- a/runtime/standard/logical_functions.cc +++ b/runtime/standard/logical_functions.cc @@ -14,16 +14,19 @@ #include "runtime/standard/logical_functions.h" +#include + #include "absl/status/status.h" -#include "absl/strings/string_view.h" +#include "absl/status/statusor.h" +#include "absl/types/span.h" #include "base/builtins.h" -#include "base/function_adapter.h" +#include "common/function_descriptor.h" +#include "common/kind.h" #include "common/value.h" #include "internal/status_macros.h" #include "runtime/function.h" #include "runtime/function_registry.h" #include "runtime/internal/errors.h" -#include "runtime/register_function_helper.h" #include "runtime/runtime_options.h" namespace cel { @@ -31,40 +34,67 @@ namespace { using ::cel::runtime_internal::CreateNoMatchingOverloadError; -Value NotStrictlyFalseImpl(const Value& value, - const Function::InvokeContext& context) { - if (value.IsBool()) { - return value; +class LogicalNotFunction final : public Function { + public: + absl::StatusOr Invoke(absl::Span args, + const InvokeContext&) const override { + if (args.size() != 1) { + return absl::InvalidArgumentError( + "unexpected number of arguments for unary function"); + } + if (!args[0].IsBool()) { + return absl::InvalidArgumentError("expected bool value"); + } + return BoolValue(!args[0].GetBool().NativeValue()); } +}; - if (value.IsError() || value.IsUnknown()) { - return TrueValue(); - } +class NotStrictlyFalseFunction final : public Function { + public: + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + if (args.size() != 1) { + return absl::InvalidArgumentError( + "unexpected number of arguments for unary function"); + } + const Value& value = args[0]; + if (value.IsBool()) { + return value; + } - // Should only accept bool unknown or error. - return ErrorValue::From( - CreateNoMatchingOverloadError(builtin::kNotStrictlyFalse), - context.arena()); -} + if (value.IsError() || value.IsUnknown()) { + return TrueValue(); + } + + // Should only accept bool unknown or error. + return ErrorValue::From( + CreateNoMatchingOverloadError(builtin::kNotStrictlyFalse), + context.arena()); + } +}; } // namespace absl::Status RegisterLogicalFunctions(FunctionRegistry& registry, const RuntimeOptions& options) { // logical NOT - CEL_RETURN_IF_ERROR( - (RegisterHelper>::RegisterGlobalOverload( - builtin::kNot, [](bool value) -> bool { return !value; }, registry))); + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(builtin::kNot, /*is_receiver_style=*/false, + {Kind::kBool}), + std::make_unique())); // Strictness - using StrictnessHelper = RegisterHelper>; - CEL_RETURN_IF_ERROR(StrictnessHelper::RegisterNonStrictOverload( - builtin::kNotStrictlyFalse, &NotStrictlyFalseImpl, registry)); - - CEL_RETURN_IF_ERROR(StrictnessHelper::RegisterNonStrictOverload( - builtin::kNotStrictlyFalseDeprecated, &NotStrictlyFalseImpl, registry)); + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(builtin::kNotStrictlyFalse, + /*is_receiver_style=*/false, {Kind::kAny}, + /*is_strict=*/false), + std::make_unique())); - return absl::OkStatus(); + return registry.Register( + FunctionDescriptor(builtin::kNotStrictlyFalseDeprecated, + /*is_receiver_style=*/false, {Kind::kAny}, + /*is_strict=*/false), + std::make_unique()); } } // namespace cel diff --git a/runtime/standard/regex_functions.cc b/runtime/standard/regex_functions.cc index e51ef821f..4c4075b7b 100644 --- a/runtime/standard/regex_functions.cc +++ b/runtime/standard/regex_functions.cc @@ -13,10 +13,14 @@ // limitations under the License. #include "runtime/standard/regex_functions.h" +#include + #include "absl/status/status.h" -#include "absl/strings/string_view.h" +#include "absl/status/statusor.h" +#include "absl/types/span.h" #include "base/builtins.h" -#include "base/function_adapter.h" +#include "common/function_descriptor.h" +#include "common/kind.h" #include "common/value.h" #include "internal/re2_options.h" #include "internal/status_macros.h" @@ -26,29 +30,43 @@ #include "re2/re2.h" namespace cel { -namespace {} // namespace +namespace { + +class RegexMatchFunction final : public Function { + public: + explicit RegexMatchFunction(int max_size) : max_size_(max_size) {} + + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + if (args.size() != 2) { + return absl::InvalidArgumentError( + "unexpected number of arguments for binary function"); + } + if (!args[0].IsString() || !args[1].IsString()) { + return absl::InvalidArgumentError("expected string value"); + } + RE2 re2(args[1].GetString().ToString(), cel::internal::MakeRE2Options()); + CEL_RETURN_IF_ERROR(cel::internal::CheckRE2(re2, max_size_)) + .With(ErrorValueReturn(context.arena())); + return BoolValue(RE2::PartialMatch(args[0].GetString().ToString(), re2)); + } + + private: + int max_size_; +}; + +} // namespace absl::Status RegisterRegexFunctions(FunctionRegistry& registry, const RuntimeOptions& options) { if (options.enable_regex) { - auto regex_matches = [max_size = options.regex_max_program_size]( - const StringValue& target, - const StringValue& regex, - const Function::InvokeContext& context) -> Value { - RE2 re2(regex.ToString(), cel::internal::MakeRE2Options()); - CEL_RETURN_IF_ERROR(cel::internal::CheckRE2(re2, max_size)) - .With(ErrorValueReturn(context.arena())); - return BoolValue(RE2::PartialMatch(target.ToString(), re2)); - }; - // bind str.matches(re) and matches(str, re) for (bool receiver_style : {true, false}) { - using MatchFnAdapter = - BinaryFunctionAdapter; - CEL_RETURN_IF_ERROR( - registry.Register(MatchFnAdapter::CreateDescriptor( - cel::builtin::kRegexMatch, receiver_style), - MatchFnAdapter::WrapFunction(regex_matches))); + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(cel::builtin::kRegexMatch, receiver_style, + {Kind::kString, Kind::kString}), + std::make_unique( + options.regex_max_program_size))); } } // if options.enable_regex diff --git a/runtime/standard/string_functions.cc b/runtime/standard/string_functions.cc index 2bcfe185c..f383ee75b 100644 --- a/runtime/standard/string_functions.cc +++ b/runtime/standard/string_functions.cc @@ -14,94 +14,109 @@ #include "runtime/standard/string_functions.h" -#include +#include -#include "absl/base/nullability.h" #include "absl/status/status.h" #include "absl/status/statusor.h" -#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "base/builtins.h" -#include "base/function_adapter.h" +#include "common/function_descriptor.h" +#include "common/kind.h" #include "common/value.h" +#include "common/value_kind.h" #include "internal/status_macros.h" +#include "runtime/function.h" #include "runtime/function_registry.h" #include "runtime/runtime_options.h" -#include "google/protobuf/arena.h" -#include "google/protobuf/descriptor.h" -#include "google/protobuf/message.h" namespace cel { namespace { -// Concatenation for string type. -absl::StatusOr ConcatString( - const StringValue& value1, const StringValue& value2, - const google::protobuf::DescriptorPool* absl_nonnull, - google::protobuf::MessageFactory* absl_nonnull, google::protobuf::Arena* absl_nonnull arena) { - return StringValue::Concat(value1, value2, arena); -} - -// Concatenation for bytes type. -absl::StatusOr ConcatBytes( - const BytesValue& value1, const BytesValue& value2, - const google::protobuf::DescriptorPool* absl_nonnull, - google::protobuf::MessageFactory* absl_nonnull, google::protobuf::Arena* absl_nonnull arena) { - return BytesValue::Concat(value1, value2, arena); -} - -bool StringContains(const StringValue& value, const StringValue& substr) { - return value.Contains(substr); -} +class StringFunction final : public Function { + public: + enum class Op { kContains, kEndsWith, kStartsWith, kConcat, kSize }; + + explicit StringFunction(Op op) : op_(op) {} + + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + if (op_ == Op::kSize) { + if (args.size() != 1) { + return absl::InvalidArgumentError( + "unexpected number of arguments for unary function"); + } + switch (args[0].kind()) { + case ValueKind::kString: + return IntValue(args[0].GetString().Size()); + case ValueKind::kBytes: + return IntValue(args[0].GetBytes().Size()); + default: + return absl::InvalidArgumentError("expected string or bytes value"); + } + } + + if (args.size() != 2) { + return absl::InvalidArgumentError( + "unexpected number of arguments for binary function"); + } + if (op_ == Op::kConcat) { + if (args[0].kind() != args[1].kind()) { + return absl::InvalidArgumentError("expected matching types for concat"); + } + switch (args[0].kind()) { + case ValueKind::kString: + return StringValue::Concat(args[0].GetString(), args[1].GetString(), + context.arena()); + case ValueKind::kBytes: + return BytesValue::Concat(args[0].GetBytes(), args[1].GetBytes(), + context.arena()); + default: + return absl::InvalidArgumentError("expected string or bytes value"); + } + } + + if (!args[0].IsString() || !args[1].IsString()) { + return absl::InvalidArgumentError("expected string value"); + } + const StringValue& value = args[0].GetString(); + const StringValue& substr = args[1].GetString(); + switch (op_) { + case Op::kContains: + return BoolValue(value.Contains(substr)); + case Op::kEndsWith: + return BoolValue(value.EndsWith(substr)); + case Op::kStartsWith: + return BoolValue(value.StartsWith(substr)); + case Op::kConcat: + case Op::kSize: + return absl::InternalError("unreachable"); + } + } -bool StringEndsWith(const StringValue& value, const StringValue& suffix) { - return value.EndsWith(suffix); -} - -bool StringStartsWith(const StringValue& value, const StringValue& prefix) { - return value.StartsWith(prefix); -} + private: + Op op_; +}; absl::Status RegisterSizeFunctions(FunctionRegistry& registry) { - // String size - auto size_func = [](const StringValue& value) -> int64_t { - return value.Size(); - }; - - // Support global and receiver style size() operations on strings. - using StrSizeFnAdapter = UnaryFunctionAdapter; - CEL_RETURN_IF_ERROR(StrSizeFnAdapter::RegisterGlobalOverload( - cel::builtin::kSize, size_func, registry)); - - CEL_RETURN_IF_ERROR(StrSizeFnAdapter::RegisterMemberOverload( - cel::builtin::kSize, size_func, registry)); - - // Bytes size - auto bytes_size_func = [](const BytesValue& value) -> int64_t { - return value.Size(); - }; - - // Support global and receiver style size() operations on bytes. - using BytesSizeFnAdapter = UnaryFunctionAdapter; - CEL_RETURN_IF_ERROR(BytesSizeFnAdapter::RegisterGlobalOverload( - cel::builtin::kSize, bytes_size_func, registry)); - - return BytesSizeFnAdapter::RegisterMemberOverload(cel::builtin::kSize, - bytes_size_func, registry); + for (bool receiver_style : {true, false}) { + for (Kind kind : {Kind::kString, Kind::kBytes}) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(cel::builtin::kSize, receiver_style, {kind}), + std::make_unique(StringFunction::Op::kSize))); + } + } + return absl::OkStatus(); } absl::Status RegisterConcatFunctions(FunctionRegistry& registry) { - using StrCatFnAdapter = - BinaryFunctionAdapter, const StringValue&, - const StringValue&>; - CEL_RETURN_IF_ERROR(StrCatFnAdapter::RegisterGlobalOverload( - cel::builtin::kAdd, &ConcatString, registry)); - - using BytesCatFnAdapter = - BinaryFunctionAdapter, const BytesValue&, - const BytesValue&>; - return BytesCatFnAdapter::RegisterGlobalOverload(cel::builtin::kAdd, - &ConcatBytes, registry); + for (Kind kind : {Kind::kString, Kind::kBytes}) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(cel::builtin::kAdd, /*is_receiver_style=*/false, + {kind, kind}), + std::make_unique(StringFunction::Op::kConcat))); + } + return absl::OkStatus(); } } // namespace @@ -109,24 +124,21 @@ absl::Status RegisterConcatFunctions(FunctionRegistry& registry) { absl::Status RegisterStringFunctions(FunctionRegistry& registry, const RuntimeOptions& options) { // Basic substring tests (contains, startsWith, endsWith) + static constexpr struct { + absl::string_view name; + StringFunction::Op op; + } kSubstringOps[] = { + {cel::builtin::kStringContains, StringFunction::Op::kContains}, + {cel::builtin::kStringEndsWith, StringFunction::Op::kEndsWith}, + {cel::builtin::kStringStartsWith, StringFunction::Op::kStartsWith}, + }; for (bool receiver_style : {true, false}) { - auto status = - BinaryFunctionAdapter:: - Register(cel::builtin::kStringContains, receiver_style, - StringContains, registry); - CEL_RETURN_IF_ERROR(status); - - status = - BinaryFunctionAdapter:: - Register(cel::builtin::kStringEndsWith, receiver_style, - StringEndsWith, registry); - CEL_RETURN_IF_ERROR(status); - - status = - BinaryFunctionAdapter:: - Register(cel::builtin::kStringStartsWith, receiver_style, - StringStartsWith, registry); - CEL_RETURN_IF_ERROR(status); + for (const auto& entry : kSubstringOps) { + CEL_RETURN_IF_ERROR( + registry.Register(FunctionDescriptor(entry.name, receiver_style, + {Kind::kString, Kind::kString}), + std::make_unique(entry.op))); + } } // string concatenation if enabled diff --git a/runtime/standard/time_functions.cc b/runtime/standard/time_functions.cc index d517e6f40..e9feae711 100644 --- a/runtime/standard/time_functions.cc +++ b/runtime/standard/time_functions.cc @@ -15,25 +15,28 @@ #include "runtime/standard/time_functions.h" #include -#include +#include #include #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/match.h" +#include "absl/strings/str_cat.h" #include "absl/strings/str_replace.h" #include "absl/strings/string_view.h" #include "absl/time/civil_time.h" #include "absl/time/time.h" +#include "absl/types/span.h" #include "base/builtins.h" -#include "base/function_adapter.h" +#include "common/function_descriptor.h" +#include "common/kind.h" #include "common/value.h" +#include "common/value_kind.h" #include "internal/overflow.h" #include "internal/status_macros.h" #include "runtime/function.h" #include "runtime/function_registry.h" #include "runtime/runtime_options.h" -#include "google/protobuf/arena.h" namespace cel { namespace { @@ -72,480 +75,286 @@ absl::Status FindTimeBreakdown(absl::Time timestamp, absl::string_view tz, return absl::InvalidArgumentError("Invalid timezone"); } -Value GetTimeBreakdownPart( - absl::Time timestamp, absl::string_view tz, - const std::function& - extractor_func, - google::protobuf::Arena* arena) { - absl::TimeZone::CivilInfo breakdown; - auto status = FindTimeBreakdown(timestamp, tz, &breakdown); - - if (!status.ok()) { - return ErrorValue::From(status, arena); - } - - return IntValue(extractor_func(breakdown)); -} - -Value GetFullYear(absl::Time timestamp, absl::string_view tz, - const Function::InvokeContext& context) { - return GetTimeBreakdownPart( - timestamp, tz, - [](const absl::TimeZone::CivilInfo& breakdown) { - return breakdown.cs.year(); - }, - context.arena()); -} - -Value GetMonth(absl::Time timestamp, absl::string_view tz, - const Function::InvokeContext& context) { - return GetTimeBreakdownPart( - timestamp, tz, - [](const absl::TimeZone::CivilInfo& breakdown) { - return breakdown.cs.month() - 1; - }, - context.arena()); -} - -Value GetDayOfYear(absl::Time timestamp, absl::string_view tz, - const Function::InvokeContext& context) { - return GetTimeBreakdownPart( - timestamp, tz, - [](const absl::TimeZone::CivilInfo& breakdown) { - return absl::GetYearDay(absl::CivilDay(breakdown.cs)) - 1; - }, - context.arena()); -} +class TimeAccessorFunction final : public Function { + public: + enum class Field { + kFullYear, + kMonth, + kDayOfYear, + kDayOfMonth, + kDate, + kDayOfWeek, + kHours, + kMinutes, + kSeconds, + kMilliseconds, + }; + + explicit TimeAccessorFunction(Field field) : field_(field) {} + + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + if (args.empty() || args.size() > 2) { + return absl::InvalidArgumentError( + "unexpected number of arguments for time accessor"); + } + if (args[0].IsDuration()) { + if (args.size() != 1) { + return absl::InvalidArgumentError( + "unexpected number of arguments for duration accessor"); + } + absl::Duration d = args[0].GetDuration().ToDuration(); + switch (field_) { + case Field::kHours: + return IntValue(absl::ToInt64Hours(d)); + case Field::kMinutes: + return IntValue(absl::ToInt64Minutes(d)); + case Field::kSeconds: + return IntValue(absl::ToInt64Seconds(d)); + case Field::kMilliseconds: { + constexpr int64_t millis_per_second = 1000L; + return IntValue(absl::ToInt64Milliseconds(d) % millis_per_second); + } + default: + return absl::InvalidArgumentError( + "unsupported accessor field for duration"); + } + } -Value GetDayOfMonth(absl::Time timestamp, absl::string_view tz, - const Function::InvokeContext& context) { - return GetTimeBreakdownPart( - timestamp, tz, - [](const absl::TimeZone::CivilInfo& breakdown) { - return breakdown.cs.day() - 1; - }, - context.arena()); -} + if (!args[0].IsTimestamp()) { + return absl::InvalidArgumentError("expected timestamp or duration value"); + } + std::string tz; + if (args.size() == 2) { + if (!args[1].IsString()) { + return absl::InvalidArgumentError("expected string timezone"); + } + tz = args[1].GetString().ToString(); + } -Value GetDate(absl::Time timestamp, absl::string_view tz, - const Function::InvokeContext& context) { - return GetTimeBreakdownPart( - timestamp, tz, - [](const absl::TimeZone::CivilInfo& breakdown) { - return breakdown.cs.day(); - }, - context.arena()); -} + absl::TimeZone::CivilInfo breakdown; + absl::Status status = + FindTimeBreakdown(args[0].GetTimestamp().ToTime(), tz, &breakdown); + if (!status.ok()) { + return ErrorValue::From(status, context.arena()); + } -Value GetDayOfWeek(absl::Time timestamp, absl::string_view tz, - const Function::InvokeContext& context) { - return GetTimeBreakdownPart( - timestamp, tz, - [](const absl::TimeZone::CivilInfo& breakdown) { + switch (field_) { + case Field::kFullYear: + return IntValue(breakdown.cs.year()); + case Field::kMonth: + return IntValue(breakdown.cs.month() - 1); + case Field::kDayOfYear: + return IntValue(absl::GetYearDay(absl::CivilDay(breakdown.cs)) - 1); + case Field::kDayOfMonth: + return IntValue(breakdown.cs.day() - 1); + case Field::kDate: + return IntValue(breakdown.cs.day()); + case Field::kDayOfWeek: { absl::Weekday weekday = absl::GetWeekday(breakdown.cs); - // get day of week from the date in UTC, zero-based, zero for Sunday, // based on GetDayOfWeek CEL function definition. int weekday_num = static_cast(weekday); weekday_num = (weekday_num == 6) ? 0 : weekday_num + 1; - return weekday_num; - }, - context.arena()); -} - -Value GetHours(absl::Time timestamp, absl::string_view tz, - const Function::InvokeContext& context) { - return GetTimeBreakdownPart( - timestamp, tz, - [](const absl::TimeZone::CivilInfo& breakdown) { - return breakdown.cs.hour(); - }, - context.arena()); -} - -Value GetMinutes(absl::Time timestamp, absl::string_view tz, - const Function::InvokeContext& context) { - return GetTimeBreakdownPart( - timestamp, tz, - [](const absl::TimeZone::CivilInfo& breakdown) { - return breakdown.cs.minute(); - }, - context.arena()); -} - -Value GetSeconds(absl::Time timestamp, absl::string_view tz, - const Function::InvokeContext& context) { - return GetTimeBreakdownPart( - timestamp, tz, - [](const absl::TimeZone::CivilInfo& breakdown) { - return breakdown.cs.second(); - }, - context.arena()); -} - -Value GetMilliseconds(absl::Time timestamp, absl::string_view tz, - const Function::InvokeContext& context) { - return GetTimeBreakdownPart( - timestamp, tz, - [](const absl::TimeZone::CivilInfo& breakdown) { - return absl::ToInt64Milliseconds(breakdown.subsecond); - }, - context.arena()); -} - -absl::Status RegisterTimestampFunctions(FunctionRegistry& registry, - const RuntimeOptions& options) { - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kFullYear, true), - BinaryFunctionAdapter:: - WrapFunction([](absl::Time ts, const StringValue& tz, - const Function::InvokeContext& context) -> Value { - return GetFullYear(ts, tz.ToString(), context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - UnaryFunctionAdapter::CreateDescriptor( - builtin::kFullYear, true), - UnaryFunctionAdapter::WrapFunction( - [](absl::Time ts, const Function::InvokeContext& context) -> Value { - return GetFullYear(ts, "", context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kMonth, true), - BinaryFunctionAdapter:: - WrapFunction([](absl::Time ts, const StringValue& tz, - const Function::InvokeContext& context) -> Value { - return GetMonth(ts, tz.ToString(), context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - UnaryFunctionAdapter::CreateDescriptor(builtin::kMonth, - true), - UnaryFunctionAdapter::WrapFunction( - [](absl::Time ts, const Function::InvokeContext& context) -> Value { - return GetMonth(ts, "", context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kDayOfYear, true), - BinaryFunctionAdapter:: - WrapFunction([](absl::Time ts, const StringValue& tz, - const Function::InvokeContext& context) -> Value { - return GetDayOfYear(ts, tz.ToString(), context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - UnaryFunctionAdapter::CreateDescriptor( - builtin::kDayOfYear, true), - UnaryFunctionAdapter::WrapFunction( - [](absl::Time ts, const Function::InvokeContext& context) -> Value { - return GetDayOfYear(ts, "", context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kDayOfMonth, true), - BinaryFunctionAdapter:: - WrapFunction([](absl::Time ts, const StringValue& tz, - const Function::InvokeContext& context) -> Value { - return GetDayOfMonth(ts, tz.ToString(), context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - UnaryFunctionAdapter::CreateDescriptor( - builtin::kDayOfMonth, true), - UnaryFunctionAdapter::WrapFunction( - [](absl::Time ts, const Function::InvokeContext& context) -> Value { - return GetDayOfMonth(ts, "", context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kDate, true), - BinaryFunctionAdapter:: - WrapFunction([](absl::Time ts, const StringValue& tz, - const Function::InvokeContext& context) -> Value { - return GetDate(ts, tz.ToString(), context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - UnaryFunctionAdapter::CreateDescriptor(builtin::kDate, - true), - UnaryFunctionAdapter::WrapFunction( - [](absl::Time ts, const Function::InvokeContext& context) -> Value { - return GetDate(ts, "", context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kDayOfWeek, true), - BinaryFunctionAdapter:: - WrapFunction([](absl::Time ts, const StringValue& tz, - const Function::InvokeContext& context) -> Value { - return GetDayOfWeek(ts, tz.ToString(), context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - UnaryFunctionAdapter::CreateDescriptor( - builtin::kDayOfWeek, true), - UnaryFunctionAdapter::WrapFunction( - [](absl::Time ts, const Function::InvokeContext& context) -> Value { - return GetDayOfWeek(ts, "", context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kHours, true), - BinaryFunctionAdapter:: - WrapFunction([](absl::Time ts, const StringValue& tz, - const Function::InvokeContext& context) -> Value { - return GetHours(ts, tz.ToString(), context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - UnaryFunctionAdapter::CreateDescriptor(builtin::kHours, - true), - UnaryFunctionAdapter::WrapFunction( - [](absl::Time ts, const Function::InvokeContext& context) -> Value { - return GetHours(ts, "", context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kMinutes, true), - BinaryFunctionAdapter:: - WrapFunction([](absl::Time ts, const StringValue& tz, - const Function::InvokeContext& context) -> Value { - return GetMinutes(ts, tz.ToString(), context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - UnaryFunctionAdapter::CreateDescriptor( - builtin::kMinutes, true), - UnaryFunctionAdapter::WrapFunction( - [](absl::Time ts, const Function::InvokeContext& context) -> Value { - return GetMinutes(ts, "", context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kSeconds, true), - BinaryFunctionAdapter:: - WrapFunction([](absl::Time ts, const StringValue& tz, - const Function::InvokeContext& context) -> Value { - return GetSeconds(ts, tz.ToString(), context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - UnaryFunctionAdapter::CreateDescriptor( - builtin::kSeconds, true), - UnaryFunctionAdapter::WrapFunction( - [](absl::Time ts, const Function::InvokeContext& context) -> Value { - return GetSeconds(ts, "", context); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kMilliseconds, true), - BinaryFunctionAdapter:: - WrapFunction([](absl::Time ts, const StringValue& tz, - const Function::InvokeContext& context) -> Value { - return GetMilliseconds(ts, tz.ToString(), context); - }))); - - return registry.Register( - UnaryFunctionAdapter::CreateDescriptor( - builtin::kMilliseconds, true), - UnaryFunctionAdapter::WrapFunction( - [](absl::Time ts, const Function::InvokeContext& context) -> Value { - return GetMilliseconds(ts, "", context); - })); -} + return IntValue(weekday_num); + } + case Field::kHours: + return IntValue(breakdown.cs.hour()); + case Field::kMinutes: + return IntValue(breakdown.cs.minute()); + case Field::kSeconds: + return IntValue(breakdown.cs.second()); + case Field::kMilliseconds: + return IntValue(absl::ToInt64Milliseconds(breakdown.subsecond)); + } + } -absl::Status RegisterCheckedTimeArithmeticFunctions( - FunctionRegistry& registry) { - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter::CreateDescriptor(builtin::kAdd, - false), - BinaryFunctionAdapter, absl::Time, absl::Duration>:: - WrapFunction([](absl::Time t1, absl::Duration d2, - const Function::InvokeContext& context) - -> absl::StatusOr { - auto sum = cel::internal::CheckedAdd(t1, d2); - if (!sum.ok()) { - return ErrorValue::From(sum.status(), context.arena()); - } - return TimestampValue(*sum); - }))); + private: + Field field_; +}; - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter, absl::Duration, - absl::Time>::CreateDescriptor(builtin::kAdd, false), - BinaryFunctionAdapter, absl::Duration, absl::Time>:: - WrapFunction([](absl::Duration d2, absl::Time t1, - const Function::InvokeContext& context) - -> absl::StatusOr { - auto sum = cel::internal::CheckedAdd(t1, d2); - if (!sum.ok()) { - return ErrorValue::From(sum.status(), context.arena()); - } - return TimestampValue(*sum); - }))); +class TimeArithmeticFunction final : public Function { + public: + enum class Op { kAdd, kSubtract }; - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter, absl::Duration, - absl::Duration>::CreateDescriptor(builtin::kAdd, - false), - BinaryFunctionAdapter, absl::Duration, - absl::Duration>:: - WrapFunction([](absl::Duration d1, absl::Duration d2, - const Function::InvokeContext& context) - -> absl::StatusOr { - auto sum = cel::internal::CheckedAdd(d1, d2); - if (!sum.ok()) { - return ErrorValue::From(sum.status(), context.arena()); - } - return DurationValue(*sum); - }))); + TimeArithmeticFunction(Op op, bool checked) : op_(op), checked_(checked) {} - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter, absl::Time, absl::Duration>:: - CreateDescriptor(builtin::kSubtract, false), - BinaryFunctionAdapter, absl::Time, absl::Duration>:: - WrapFunction([](absl::Time t1, absl::Duration d2, - const Function::InvokeContext& context) - -> absl::StatusOr { - auto diff = cel::internal::CheckedSub(t1, d2); - if (!diff.ok()) { - return ErrorValue::From(diff.status(), context.arena()); - } - return TimestampValue(*diff); - }))); + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + if (args.size() != 2) { + return absl::InvalidArgumentError( + "unexpected number of arguments for binary function"); + } + ValueKind lhs_kind = args[0].kind(); + ValueKind rhs_kind = args[1].kind(); + if (op_ == Op::kAdd) { + if (lhs_kind == ValueKind::kTimestamp && + rhs_kind == ValueKind::kDuration) { + absl::Time t1 = args[0].GetTimestamp().ToTime(); + absl::Duration d2 = args[1].GetDuration().ToDuration(); + if (!checked_) { + return UnsafeTimestampValue(t1 + d2); + } + auto sum = cel::internal::CheckedAdd(t1, d2); + if (!sum.ok()) { + return ErrorValue::From(sum.status(), context.arena()); + } + return TimestampValue(*sum); + } + if (lhs_kind == ValueKind::kDuration && + rhs_kind == ValueKind::kTimestamp) { + absl::Duration d2 = args[0].GetDuration().ToDuration(); + absl::Time t1 = args[1].GetTimestamp().ToTime(); + if (!checked_) { + return UnsafeTimestampValue(t1 + d2); + } + auto sum = cel::internal::CheckedAdd(t1, d2); + if (!sum.ok()) { + return ErrorValue::From(sum.status(), context.arena()); + } + return TimestampValue(*sum); + } + if (lhs_kind == ValueKind::kDuration && + rhs_kind == ValueKind::kDuration) { + absl::Duration d1 = args[0].GetDuration().ToDuration(); + absl::Duration d2 = args[1].GetDuration().ToDuration(); + if (!checked_) { + return UnsafeDurationValue(d1 + d2); + } + auto sum = cel::internal::CheckedAdd(d1, d2); + if (!sum.ok()) { + return ErrorValue::From(sum.status(), context.arena()); + } + return DurationValue(*sum); + } + return absl::InvalidArgumentError( + "expected timestamp/duration arguments for add"); + } - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter, absl::Time, - absl::Time>::CreateDescriptor(builtin::kSubtract, - false), - BinaryFunctionAdapter, absl::Time, absl::Time>:: - WrapFunction([](absl::Time t1, absl::Time t2, - const Function::InvokeContext& context) - -> absl::StatusOr { - auto diff = cel::internal::CheckedSub(t1, t2); - if (!diff.ok()) { - return ErrorValue::From(diff.status(), context.arena()); - } - return DurationValue(*diff); - }))); + // Op::kSubtract + if (lhs_kind == ValueKind::kTimestamp && rhs_kind == ValueKind::kDuration) { + absl::Time t1 = args[0].GetTimestamp().ToTime(); + absl::Duration d2 = args[1].GetDuration().ToDuration(); + if (!checked_) { + return UnsafeTimestampValue(t1 - d2); + } + auto diff = cel::internal::CheckedSub(t1, d2); + if (!diff.ok()) { + return ErrorValue::From(diff.status(), context.arena()); + } + return TimestampValue(*diff); + } + if (lhs_kind == ValueKind::kTimestamp && + rhs_kind == ValueKind::kTimestamp) { + absl::Time t1 = args[0].GetTimestamp().ToTime(); + absl::Time t2 = args[1].GetTimestamp().ToTime(); + if (!checked_) { + return UnsafeDurationValue(t1 - t2); + } + auto diff = cel::internal::CheckedSub(t1, t2); + if (!diff.ok()) { + return ErrorValue::From(diff.status(), context.arena()); + } + return DurationValue(*diff); + } + if (lhs_kind == ValueKind::kDuration && rhs_kind == ValueKind::kDuration) { + absl::Duration d1 = args[0].GetDuration().ToDuration(); + absl::Duration d2 = args[1].GetDuration().ToDuration(); + if (!checked_) { + return UnsafeDurationValue(d1 - d2); + } + auto diff = cel::internal::CheckedSub(d1, d2); + if (!diff.ok()) { + return ErrorValue::From(diff.status(), context.arena()); + } + return DurationValue(*diff); + } + return absl::InvalidArgumentError( + "expected timestamp/duration arguments for subtract"); + } - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter< - absl::StatusOr, absl::Duration, - absl::Duration>::CreateDescriptor(builtin::kSubtract, false), - BinaryFunctionAdapter, absl::Duration, - absl::Duration>:: - WrapFunction([](absl::Duration d1, absl::Duration d2, - const Function::InvokeContext& context) - -> absl::StatusOr { - auto diff = cel::internal::CheckedSub(d1, d2); - if (!diff.ok()) { - return ErrorValue::From(diff.status(), context.arena()); - } - return DurationValue(*diff); - }))); + private: + Op op_; + bool checked_; +}; +absl::Status RegisterTimestampFunctions(FunctionRegistry& registry, + const RuntimeOptions& options) { + static constexpr struct { + absl::string_view name; + TimeAccessorFunction::Field field; + } kTimestampAccessors[] = { + {builtin::kFullYear, TimeAccessorFunction::Field::kFullYear}, + {builtin::kMonth, TimeAccessorFunction::Field::kMonth}, + {builtin::kDayOfYear, TimeAccessorFunction::Field::kDayOfYear}, + {builtin::kDayOfMonth, TimeAccessorFunction::Field::kDayOfMonth}, + {builtin::kDate, TimeAccessorFunction::Field::kDate}, + {builtin::kDayOfWeek, TimeAccessorFunction::Field::kDayOfWeek}, + {builtin::kHours, TimeAccessorFunction::Field::kHours}, + {builtin::kMinutes, TimeAccessorFunction::Field::kMinutes}, + {builtin::kSeconds, TimeAccessorFunction::Field::kSeconds}, + {builtin::kMilliseconds, TimeAccessorFunction::Field::kMilliseconds}, + }; + for (const auto& entry : kTimestampAccessors) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(entry.name, /*is_receiver_style=*/true, + {Kind::kTimestamp, Kind::kString}), + std::make_unique(entry.field))); + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(entry.name, /*is_receiver_style=*/true, + {Kind::kTimestamp}), + std::make_unique(entry.field))); + } return absl::OkStatus(); } -absl::Status RegisterUncheckedTimeArithmeticFunctions( - FunctionRegistry& registry) { - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter::CreateDescriptor(builtin::kAdd, - false), - BinaryFunctionAdapter::WrapFunction( - [](absl::Time t1, absl::Duration d2) -> Value { - return UnsafeTimestampValue(t1 + d2); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter::CreateDescriptor(builtin::kAdd, false), - BinaryFunctionAdapter::WrapFunction( - [](absl::Duration d2, absl::Time t1) -> Value { - return UnsafeTimestampValue(t1 + d2); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter::CreateDescriptor(builtin::kAdd, - false), - BinaryFunctionAdapter:: - WrapFunction([](absl::Duration d1, absl::Duration d2) -> Value { - return UnsafeDurationValue(d1 + d2); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kSubtract, false), - - BinaryFunctionAdapter::WrapFunction( - - [](absl::Time t1, absl::Duration d2) -> Value { - return UnsafeTimestampValue(t1 - d2); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter::CreateDescriptor( - builtin::kSubtract, false), - BinaryFunctionAdapter::WrapFunction( - - [](absl::Time t1, absl::Time t2) -> Value { - return UnsafeDurationValue(t1 - t2); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - BinaryFunctionAdapter:: - CreateDescriptor(builtin::kSubtract, false), - BinaryFunctionAdapter:: - WrapFunction([](absl::Duration d1, absl::Duration d2) -> Value { - return UnsafeDurationValue(d1 - d2); - }))); - +absl::Status RegisterTimeArithmeticFunctions(FunctionRegistry& registry, + bool checked) { + static constexpr struct { + absl::string_view name; + TimeArithmeticFunction::Op op; + Kind lhs; + Kind rhs; + } kOverloads[] = { + {builtin::kAdd, TimeArithmeticFunction::Op::kAdd, Kind::kTimestamp, + Kind::kDuration}, + {builtin::kAdd, TimeArithmeticFunction::Op::kAdd, Kind::kDuration, + Kind::kTimestamp}, + {builtin::kAdd, TimeArithmeticFunction::Op::kAdd, Kind::kDuration, + Kind::kDuration}, + {builtin::kSubtract, TimeArithmeticFunction::Op::kSubtract, + Kind::kTimestamp, Kind::kDuration}, + {builtin::kSubtract, TimeArithmeticFunction::Op::kSubtract, + Kind::kTimestamp, Kind::kTimestamp}, + {builtin::kSubtract, TimeArithmeticFunction::Op::kSubtract, + Kind::kDuration, Kind::kDuration}, + }; + for (const auto& entry : kOverloads) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(entry.name, /*is_receiver_style=*/false, + {entry.lhs, entry.rhs}), + std::make_unique(entry.op, checked))); + } return absl::OkStatus(); } absl::Status RegisterDurationFunctions(FunctionRegistry& registry) { - // duration breakdown accessor functions - using DurationAccessorFunction = - UnaryFunctionAdapter; - CEL_RETURN_IF_ERROR(registry.Register( - DurationAccessorFunction::CreateDescriptor(builtin::kHours, true), - DurationAccessorFunction::WrapFunction( - [](absl::Duration d) -> int64_t { return absl::ToInt64Hours(d); }))); - - CEL_RETURN_IF_ERROR(registry.Register( - DurationAccessorFunction::CreateDescriptor(builtin::kMinutes, true), - DurationAccessorFunction::WrapFunction([](absl::Duration d) -> int64_t { - return absl::ToInt64Minutes(d); - }))); - - CEL_RETURN_IF_ERROR(registry.Register( - DurationAccessorFunction::CreateDescriptor(builtin::kSeconds, true), - DurationAccessorFunction::WrapFunction([](absl::Duration d) -> int64_t { - return absl::ToInt64Seconds(d); - }))); - - return registry.Register( - DurationAccessorFunction::CreateDescriptor(builtin::kMilliseconds, true), - DurationAccessorFunction::WrapFunction([](absl::Duration d) -> int64_t { - constexpr int64_t millis_per_second = 1000L; - return absl::ToInt64Milliseconds(d) % millis_per_second; - })); + static constexpr struct { + absl::string_view name; + TimeAccessorFunction::Field field; + } kDurationAccessors[] = { + {builtin::kHours, TimeAccessorFunction::Field::kHours}, + {builtin::kMinutes, TimeAccessorFunction::Field::kMinutes}, + {builtin::kSeconds, TimeAccessorFunction::Field::kSeconds}, + {builtin::kMilliseconds, TimeAccessorFunction::Field::kMilliseconds}, + }; + for (const auto& entry : kDurationAccessors) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(entry.name, /*is_receiver_style=*/true, + {Kind::kDuration}), + std::make_unique(entry.field))); + } + return absl::OkStatus(); } } // namespace @@ -558,11 +367,8 @@ absl::Status RegisterTimeFunctions(FunctionRegistry& registry, // Special arithmetic operators for Timestamp and Duration // TODO(uncreated-issue/37): deprecate unchecked time math functions when clients no // longer depend on them. - if (options.enable_timestamp_duration_overflow_errors) { - return RegisterCheckedTimeArithmeticFunctions(registry); - } - - return RegisterUncheckedTimeArithmeticFunctions(registry); + return RegisterTimeArithmeticFunctions( + registry, options.enable_timestamp_duration_overflow_errors); } } // namespace cel diff --git a/runtime/standard/type_conversion_functions.cc b/runtime/standard/type_conversion_functions.cc index 2c5130aa3..9302e6e0c 100644 --- a/runtime/standard/type_conversion_functions.cc +++ b/runtime/standard/type_conversion_functions.cc @@ -16,6 +16,7 @@ #include #include +#include #include // NOLINT (required for std::to_chars_result) #include @@ -25,9 +26,12 @@ #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" +#include "absl/types/span.h" #include "base/builtins.h" -#include "base/function_adapter.h" +#include "common/function_descriptor.h" +#include "common/kind.h" #include "common/value.h" +#include "common/value_kind.h" #include "internal/overflow.h" #include "internal/status_macros.h" #include "internal/time.h" @@ -80,417 +84,401 @@ Value LegacyFormatDouble(double v, const Function::InvokeContext& context) { return StringValue::From(absl::StrCat(v), context.arena()); } -absl::Status RegisterBoolConversionFunctions(FunctionRegistry& registry, - const RuntimeOptions&) { - // bool -> bool - absl::Status status = - UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kBool, [](bool v) { return v; }, registry); - CEL_RETURN_IF_ERROR(status); - - // string -> bool - return UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kBool, - [](const StringValue& v, - const Function::InvokeContext& context) -> Value { - if ((v == "true") || (v == "True") || (v == "TRUE") || (v == "t") || - (v == "1")) { - return TrueValue(); - } else if ((v == "false") || (v == "FALSE") || (v == "False") || - (v == "f") || (v == "0")) { - return FalseValue(); - } else { - return ErrorValue::From( - absl::InvalidArgumentError( - "Type conversion error from 'string' to 'bool'"), - context.arena()); - } - }, - registry); +Value ConvertToBool(const Value& arg, const Function::InvokeContext& context) { + switch (arg.kind()) { + case ValueKind::kBool: + return arg; + case ValueKind::kString: { + const StringValue& v = arg.GetString(); + if ((v == "true") || (v == "True") || (v == "TRUE") || (v == "t") || + (v == "1")) { + return TrueValue(); + } + if ((v == "false") || (v == "FALSE") || (v == "False") || (v == "f") || + (v == "0")) { + return FalseValue(); + } + return ErrorValue::From( + absl::InvalidArgumentError( + "Type conversion error from 'string' to 'bool'"), + context.arena()); + } + default: + return ErrorValue::From( + absl::InvalidArgumentError("cannot convert value to bool"), + context.arena()); + } } -absl::Status RegisterIntConversionFunctions(FunctionRegistry& registry, - const RuntimeOptions&) { - // bool -> int - absl::Status status = - UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kInt, [](bool v) { return static_cast(v); }, - registry); - CEL_RETURN_IF_ERROR(status); - - // double -> int - status = UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kInt, - [](double v, const Function::InvokeContext& context) -> Value { - auto conv = cel::internal::CheckedDoubleToInt64(v); - if (!conv.ok()) { - return ErrorValue::From(conv.status(), context.arena()); - } - return IntValue(*conv); - }, - registry); - CEL_RETURN_IF_ERROR(status); - - // int -> int - status = UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kInt, [](int64_t v) { return v; }, registry); - CEL_RETURN_IF_ERROR(status); - - // string -> int - status = - UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kInt, - [](const StringValue& s, - const Function::InvokeContext& context) -> Value { - int64_t result; - if (!absl::SimpleAtoi(s.ToString(), &result)) { - return ErrorValue::From( - absl::InvalidArgumentError("cannot convert string to int"), - context.arena()); - } - return IntValue(result); - }, - registry); - CEL_RETURN_IF_ERROR(status); - - // time -> int - status = UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kInt, [](absl::Time t) { return absl::ToUnixSeconds(t); }, - registry); - CEL_RETURN_IF_ERROR(status); - - // uint -> int - return UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kInt, - [](uint64_t v, const Function::InvokeContext& context) -> Value { - auto conv = cel::internal::CheckedUint64ToInt64(v); - if (!conv.ok()) { - return ErrorValue::From(conv.status(), context.arena()); - } - return IntValue(*conv); - }, - registry); +Value ConvertToInt(const Value& arg, const Function::InvokeContext& context) { + switch (arg.kind()) { + case ValueKind::kInt: + return arg; + case ValueKind::kBool: + return IntValue(static_cast(arg.GetBool().NativeValue())); + case ValueKind::kDouble: { + auto conv = + cel::internal::CheckedDoubleToInt64(arg.GetDouble().NativeValue()); + if (!conv.ok()) { + return ErrorValue::From(conv.status(), context.arena()); + } + return IntValue(*conv); + } + case ValueKind::kString: { + int64_t result; + if (!absl::SimpleAtoi(arg.GetString().ToString(), &result)) { + return ErrorValue::From( + absl::InvalidArgumentError("cannot convert string to int"), + context.arena()); + } + return IntValue(result); + } + case ValueKind::kTimestamp: + return IntValue(absl::ToUnixSeconds(arg.GetTimestamp().ToTime())); + case ValueKind::kUint: { + auto conv = + cel::internal::CheckedUint64ToInt64(arg.GetUint().NativeValue()); + if (!conv.ok()) { + return ErrorValue::From(conv.status(), context.arena()); + } + return IntValue(*conv); + } + default: + return ErrorValue::From( + absl::InvalidArgumentError("cannot convert value to int"), + context.arena()); + } } -absl::Status RegisterStringConversionFunctions(FunctionRegistry& registry, - const RuntimeOptions& options) { - // May be optionally disabled to reduce potential allocs. - if (!options.enable_string_conversion) { - return absl::OkStatus(); +Value ConvertToUint(const Value& arg, const Function::InvokeContext& context) { + switch (arg.kind()) { + case ValueKind::kUint: + return arg; + case ValueKind::kDouble: { + auto conv = + cel::internal::CheckedDoubleToUint64(arg.GetDouble().NativeValue()); + if (!conv.ok()) { + return ErrorValue::From(conv.status(), context.arena()); + } + return UintValue(*conv); + } + case ValueKind::kInt: { + auto conv = + cel::internal::CheckedInt64ToUint64(arg.GetInt().NativeValue()); + if (!conv.ok()) { + return ErrorValue::From(conv.status(), context.arena()); + } + return UintValue(*conv); + } + case ValueKind::kString: { + uint64_t result; + if (!absl::SimpleAtoi(arg.GetString().ToString(), &result)) { + return ErrorValue::From( + absl::InvalidArgumentError("cannot convert string to uint"), + context.arena()); + } + return UintValue(result); + } + default: + return ErrorValue::From( + absl::InvalidArgumentError("cannot convert value to uint"), + context.arena()); } - - absl::Status status = - UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kString, - - [](const BytesValue& value, - const Function::InvokeContext& context) -> Value { - auto valid = value.NativeValue([](const auto& value) -> bool { - return internal::Utf8IsValid(value); - }); - if (!valid) { - return ErrorValue::From( - absl::InvalidArgumentError("malformed UTF-8 bytes"), - context.arena()); - } - return StringValue(value); - }, - registry); - CEL_RETURN_IF_ERROR(status); - - // bool -> string - status = UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kString, - [](bool value) -> StringValue { - return StringValue::WrapUnsafe(value ? "true" : "false"); - }, - registry); - CEL_RETURN_IF_ERROR(status); - - // double -> string - status = UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kString, - (options.enable_precision_preserving_double_format ? &FormatDouble - : &LegacyFormatDouble), - registry); - CEL_RETURN_IF_ERROR(status); - - // int -> string - status = UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kString, - [](int64_t value, const Function::InvokeContext& context) -> StringValue { - return StringValue::From(absl::StrCat(value), context.arena()); - }, - registry); - CEL_RETURN_IF_ERROR(status); - - // string -> string - status = - UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kString, - [](StringValue value) -> StringValue { return value; }, registry); - CEL_RETURN_IF_ERROR(status); - - // uint -> string - status = UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kString, - [](uint64_t value, - const Function::InvokeContext& context) -> StringValue { - return StringValue::From(absl::StrCat(value), context.arena()); - }, - registry); - CEL_RETURN_IF_ERROR(status); - - // duration -> string - status = UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kString, - [](absl::Duration value, - const Function::InvokeContext& context) -> Value { - auto encode = EncodeDurationToJson(value); - if (!encode.ok()) { - return ErrorValue::From(encode.status(), context.arena()); - } - return StringValue::From(*encode, context.arena()); - }, - registry); - CEL_RETURN_IF_ERROR(status); - - // timestamp -> string - return UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kString, - [](absl::Time value, const Function::InvokeContext& context) -> Value { - auto encode = EncodeTimestampToJson(value); - if (!encode.ok()) { - return ErrorValue::From(encode.status(), context.arena()); - } - return StringValue::From(*encode, context.arena()); - }, - registry); } -absl::Status RegisterUintConversionFunctions(FunctionRegistry& registry, - const RuntimeOptions&) { - // double -> uint - absl::Status status = - UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kUint, - [](double v, const Function::InvokeContext& context) -> Value { - auto conv = cel::internal::CheckedDoubleToUint64(v); - if (!conv.ok()) { - return ErrorValue::From(conv.status(), context.arena()); - } - return UintValue(*conv); - }, - registry); - CEL_RETURN_IF_ERROR(status); - - // int -> uint - status = UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kUint, - [](int64_t v, const Function::InvokeContext& context) -> Value { - auto conv = cel::internal::CheckedInt64ToUint64(v); - if (!conv.ok()) { - return ErrorValue::From(conv.status(), context.arena()); - } - return UintValue(*conv); - }, - registry); - CEL_RETURN_IF_ERROR(status); - - // string -> uint - status = - UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kUint, - [](const StringValue& s, - const Function::InvokeContext& context) -> Value { - uint64_t result; - if (!absl::SimpleAtoi(s.ToString(), &result)) { - return ErrorValue::From( - absl::InvalidArgumentError("cannot convert string to uint"), - context.arena()); - } - return UintValue(result); - }, - registry); - CEL_RETURN_IF_ERROR(status); - - // uint -> uint - return UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kUint, [](uint64_t v) { return v; }, registry); +Value ConvertToDouble(const Value& arg, + const Function::InvokeContext& context) { + switch (arg.kind()) { + case ValueKind::kDouble: + return arg; + case ValueKind::kInt: + return DoubleValue(static_cast(arg.GetInt().NativeValue())); + case ValueKind::kUint: + return DoubleValue(static_cast(arg.GetUint().NativeValue())); + case ValueKind::kString: { + double result; + if (absl::SimpleAtod(arg.GetString().ToString(), &result)) { + return DoubleValue(result); + } + return ErrorValue::From( + absl::InvalidArgumentError("cannot convert string to double"), + context.arena()); + } + default: + return ErrorValue::From( + absl::InvalidArgumentError("cannot convert value to double"), + context.arena()); + } } -absl::Status RegisterBytesConversionFunctions(FunctionRegistry& registry, - const RuntimeOptions&) { - // bytes -> bytes - absl::Status status = - UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kBytes, - - [](BytesValue value) -> BytesValue { return value; }, registry); - CEL_RETURN_IF_ERROR(status); - - // string -> bytes - return UnaryFunctionAdapter, const StringValue&>:: - RegisterGlobalOverload( - cel::builtin::kBytes, - [](const StringValue& value) { return BytesValue(value); }, registry); +Value ConvertToString(const Value& arg, bool precision_preserving_double_format, + const Function::InvokeContext& context) { + switch (arg.kind()) { + case ValueKind::kString: + return arg; + case ValueKind::kBytes: { + const BytesValue& value = arg.GetBytes(); + auto valid = value.NativeValue([](const auto& bytes) -> bool { + return internal::Utf8IsValid(bytes); + }); + if (!valid) { + return ErrorValue::From( + absl::InvalidArgumentError("malformed UTF-8 bytes"), + context.arena()); + } + return StringValue(value); + } + case ValueKind::kBool: + return StringValue::WrapUnsafe(arg.GetBool().NativeValue() ? "true" + : "false"); + case ValueKind::kDouble: + return precision_preserving_double_format + ? FormatDouble(arg.GetDouble().NativeValue(), context) + : LegacyFormatDouble(arg.GetDouble().NativeValue(), context); + case ValueKind::kInt: + return StringValue::From(absl::StrCat(arg.GetInt().NativeValue()), + context.arena()); + case ValueKind::kUint: + return StringValue::From(absl::StrCat(arg.GetUint().NativeValue()), + context.arena()); + case ValueKind::kDuration: { + auto encode = EncodeDurationToJson(arg.GetDuration().ToDuration()); + if (!encode.ok()) { + return ErrorValue::From(encode.status(), context.arena()); + } + return StringValue::From(*encode, context.arena()); + } + case ValueKind::kTimestamp: { + auto encode = EncodeTimestampToJson(arg.GetTimestamp().ToTime()); + if (!encode.ok()) { + return ErrorValue::From(encode.status(), context.arena()); + } + return StringValue::From(*encode, context.arena()); + } + default: + return ErrorValue::From( + absl::InvalidArgumentError("cannot convert value to string"), + context.arena()); + } } -absl::Status RegisterDoubleConversionFunctions(FunctionRegistry& registry, - const RuntimeOptions&) { - // double -> double - absl::Status status = - UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kDouble, [](double v) { return v; }, registry); - CEL_RETURN_IF_ERROR(status); - - // int -> double - status = UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kDouble, [](int64_t v) { return static_cast(v); }, - registry); - CEL_RETURN_IF_ERROR(status); - - // string -> double - status = - UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kDouble, - [](const StringValue& s, - const Function::InvokeContext& context) -> Value { - double result; - if (absl::SimpleAtod(s.ToString(), &result)) { - return DoubleValue(result); - } else { - return ErrorValue::From( - absl::InvalidArgumentError("cannot convert string to double"), - context.arena()); - } - }, - registry); - CEL_RETURN_IF_ERROR(status); - - // uint -> double - return UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kDouble, [](uint64_t v) { return static_cast(v); }, - registry); +Value ConvertToDuration(const Value& arg, + const Function::InvokeContext& context) { + switch (arg.kind()) { + case ValueKind::kDuration: + return UnsafeDurationValue(arg.GetDuration().ToDuration()); + case ValueKind::kString: { + absl::Duration d; + if (!absl::ParseDuration(arg.GetString().ToString(), &d)) { + return ErrorValue::From( + absl::InvalidArgumentError("String to Duration conversion failed"), + context.arena()); + } + auto status = internal::ValidateDuration(d); + if (!status.ok()) { + return ErrorValue::From(std::move(status), context.arena()); + } + return DurationValue(d); + } + default: + return ErrorValue::From( + absl::InvalidArgumentError("cannot convert value to duration"), + context.arena()); + } } -Value CreateDurationFromString(const StringValue& dur_str, - const Function::InvokeContext& context) { - absl::Duration d; - if (!absl::ParseDuration(dur_str.ToString(), &d)) { - return ErrorValue::From( - absl::InvalidArgumentError("String to Duration conversion failed"), - context.arena()); +Value ConvertToTimestamp(const Value& arg, + bool enable_timestamp_duration_overflow_errors, + const Function::InvokeContext& context) { + switch (arg.kind()) { + case ValueKind::kTimestamp: + return UnsafeTimestampValue(arg.GetTimestamp().ToTime()); + case ValueKind::kInt: { + absl::Time ts = absl::FromUnixSeconds(arg.GetInt().NativeValue()); + if (enable_timestamp_duration_overflow_errors) { + if (ts < MinTimestamp() || ts > MaxTimestamp()) { + return ErrorValue::From(absl::OutOfRangeError("timestamp overflow"), + context.arena()); + } + } + return UnsafeTimestampValue(ts); + } + case ValueKind::kString: { + absl::Time ts; + if (!absl::ParseTime(absl::RFC3339_full, arg.GetString().ToString(), &ts, + nullptr)) { + return ErrorValue::From( + absl::InvalidArgumentError("String to Timestamp conversion failed"), + context.arena()); + } + if (enable_timestamp_duration_overflow_errors) { + if (ts < MinTimestamp() || ts > MaxTimestamp()) { + return ErrorValue::From(absl::OutOfRangeError("timestamp overflow"), + context.arena()); + } + } + return UnsafeTimestampValue(ts); + } + default: + return ErrorValue::From( + absl::InvalidArgumentError("cannot convert value to timestamp"), + context.arena()); } +} - auto status = internal::ValidateDuration(d); - if (!status.ok()) { - return ErrorValue::From(std::move(status), context.arena()); +class TypeConversionFunction final : public Function { + public: + enum class Target { + kBool, + kInt, + kUint, + kDouble, + kString, + kBytes, + kDuration, + kTimestamp, + kDyn, + kType, + }; + + TypeConversionFunction(Target target, + bool precision_preserving_double_format = true, + bool enable_timestamp_duration_overflow_errors = false) + : target_(target), + precision_preserving_double_format_(precision_preserving_double_format), + enable_timestamp_duration_overflow_errors_( + enable_timestamp_duration_overflow_errors) {} + + absl::StatusOr Invoke(absl::Span args, + const InvokeContext& context) const override { + if (args.size() != 1) { + return absl::InvalidArgumentError( + "unexpected number of arguments for unary function"); + } + const Value& arg = args[0]; + switch (target_) { + case Target::kBool: + return ConvertToBool(arg, context); + case Target::kInt: + return ConvertToInt(arg, context); + case Target::kUint: + return ConvertToUint(arg, context); + case Target::kDouble: + return ConvertToDouble(arg, context); + case Target::kString: + return ConvertToString(arg, precision_preserving_double_format_, + context); + case Target::kBytes: + if (arg.IsBytes()) { + return arg; + } + if (arg.IsString()) { + return BytesValue(arg.GetString()); + } + return absl::InvalidArgumentError("cannot convert value to bytes"); + case Target::kDuration: + return ConvertToDuration(arg, context); + case Target::kTimestamp: + return ConvertToTimestamp( + arg, enable_timestamp_duration_overflow_errors_, context); + case Target::kDyn: + return arg; + case Target::kType: + return TypeValue(arg.GetRuntimeType()); + } } - return DurationValue(d); -} -absl::Status RegisterTimeConversionFunctions(FunctionRegistry& registry, - const RuntimeOptions& options) { - // duration() conversion from string. - CEL_RETURN_IF_ERROR( - (UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kDuration, CreateDurationFromString, registry))); - - bool enable_timestamp_duration_overflow_errors = - options.enable_timestamp_duration_overflow_errors; - - // timestamp conversion from int. - CEL_RETURN_IF_ERROR( - (UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kTimestamp, - [=](int64_t epoch_seconds, - const Function::InvokeContext& context) -> Value { - absl::Time ts = absl::FromUnixSeconds(epoch_seconds); - if (enable_timestamp_duration_overflow_errors) { - if (ts < MinTimestamp() || ts > MaxTimestamp()) { - return ErrorValue::From( - absl::OutOfRangeError("timestamp overflow"), - context.arena()); - } - } - return UnsafeTimestampValue(ts); - }, - registry))); - - // timestamp -> timestamp - CEL_RETURN_IF_ERROR( - (UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kTimestamp, - [](absl::Time value) -> Value { return UnsafeTimestampValue(value); }, - registry))); - - // duration -> duration - CEL_RETURN_IF_ERROR( - (UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kDuration, - [](absl::Duration value) -> Value { - return UnsafeDurationValue(value); - }, - registry))); - - // timestamp() conversion from string. - return UnaryFunctionAdapter:: - RegisterGlobalOverload( - cel::builtin::kTimestamp, - [=](const StringValue& time_str, - const Function::InvokeContext& context) -> Value { - absl::Time ts; - if (!absl::ParseTime(absl::RFC3339_full, time_str.ToString(), &ts, - nullptr)) { - return ErrorValue::From( - absl::InvalidArgumentError( - "String to Timestamp conversion failed"), - context.arena()); - } - if (enable_timestamp_duration_overflow_errors) { - if (ts < MinTimestamp() || ts > MaxTimestamp()) { - return ErrorValue::From( - absl::OutOfRangeError("timestamp overflow"), - context.arena()); - } - } - return UnsafeTimestampValue(ts); - }, - registry); + private: + Target target_; + bool precision_preserving_double_format_; + bool enable_timestamp_duration_overflow_errors_; +}; + +absl::Status RegisterConversionOverloads( + FunctionRegistry& registry, absl::string_view name, + TypeConversionFunction::Target target, absl::Span arg_kinds, + bool precision_preserving_double_format = true, + bool enable_timestamp_duration_overflow_errors = false) { + for (Kind kind : arg_kinds) { + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(name, /*is_receiver_style=*/false, {kind}), + std::make_unique( + target, precision_preserving_double_format, + enable_timestamp_duration_overflow_errors))); + } + return absl::OkStatus(); } } // namespace absl::Status RegisterTypeConversionFunctions(FunctionRegistry& registry, const RuntimeOptions& options) { - CEL_RETURN_IF_ERROR(RegisterBoolConversionFunctions(registry, options)); - - CEL_RETURN_IF_ERROR(RegisterBytesConversionFunctions(registry, options)); - - CEL_RETURN_IF_ERROR(RegisterDoubleConversionFunctions(registry, options)); - - CEL_RETURN_IF_ERROR(RegisterIntConversionFunctions(registry, options)); - - CEL_RETURN_IF_ERROR(RegisterStringConversionFunctions(registry, options)); - - CEL_RETURN_IF_ERROR(RegisterUintConversionFunctions(registry, options)); + static constexpr Kind kBoolKinds[] = {Kind::kBool, Kind::kString}; + CEL_RETURN_IF_ERROR(RegisterConversionOverloads( + registry, cel::builtin::kBool, TypeConversionFunction::Target::kBool, + kBoolKinds)); + + static constexpr Kind kBytesKinds[] = {Kind::kBytes, Kind::kString}; + CEL_RETURN_IF_ERROR(RegisterConversionOverloads( + registry, cel::builtin::kBytes, TypeConversionFunction::Target::kBytes, + kBytesKinds)); + + static constexpr Kind kDoubleKinds[] = {Kind::kDouble, Kind::kInt, + Kind::kString, Kind::kUint}; + CEL_RETURN_IF_ERROR(RegisterConversionOverloads( + registry, cel::builtin::kDouble, TypeConversionFunction::Target::kDouble, + kDoubleKinds)); + + static constexpr Kind kIntKinds[] = {Kind::kBool, Kind::kDouble, + Kind::kInt, Kind::kString, + Kind::kTimestamp, Kind::kUint}; + CEL_RETURN_IF_ERROR(RegisterConversionOverloads( + registry, cel::builtin::kInt, TypeConversionFunction::Target::kInt, + kIntKinds)); + + if (options.enable_string_conversion) { + static constexpr Kind kStringKinds[] = { + Kind::kBytes, Kind::kBool, Kind::kDouble, Kind::kInt, + Kind::kString, Kind::kUint, Kind::kDuration, Kind::kTimestamp}; + CEL_RETURN_IF_ERROR(RegisterConversionOverloads( + registry, cel::builtin::kString, + TypeConversionFunction::Target::kString, kStringKinds, + options.enable_precision_preserving_double_format)); + } - CEL_RETURN_IF_ERROR(RegisterTimeConversionFunctions(registry, options)); + static constexpr Kind kUintKinds[] = {Kind::kDouble, Kind::kInt, + Kind::kString, Kind::kUint}; + CEL_RETURN_IF_ERROR(RegisterConversionOverloads( + registry, cel::builtin::kUint, TypeConversionFunction::Target::kUint, + kUintKinds)); + + static constexpr Kind kDurationKinds[] = {Kind::kString, Kind::kDuration}; + CEL_RETURN_IF_ERROR(RegisterConversionOverloads( + registry, cel::builtin::kDuration, + TypeConversionFunction::Target::kDuration, kDurationKinds)); + + static constexpr Kind kTimestampKinds[] = {Kind::kInt, Kind::kTimestamp, + Kind::kString}; + CEL_RETURN_IF_ERROR(RegisterConversionOverloads( + registry, cel::builtin::kTimestamp, + TypeConversionFunction::Target::kTimestamp, kTimestampKinds, + /*precision_preserving_double_format=*/true, + options.enable_timestamp_duration_overflow_errors)); // dyn() identity function. // TODO(issues/102): strip dyn() function references at type-check time. - absl::Status status = - UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kDyn, [](const Value& value) -> Value { return value; }, - registry); - CEL_RETURN_IF_ERROR(status); + CEL_RETURN_IF_ERROR(registry.Register( + FunctionDescriptor(cel::builtin::kDyn, /*is_receiver_style=*/false, + {Kind::kAny}), + std::make_unique( + TypeConversionFunction::Target::kDyn))); // type(dyn) -> type - return UnaryFunctionAdapter::RegisterGlobalOverload( - cel::builtin::kType, - [](const Value& value) { return TypeValue(value.GetRuntimeType()); }, - registry); + return registry.Register( + FunctionDescriptor(cel::builtin::kType, /*is_receiver_style=*/false, + {Kind::kAny}), + std::make_unique( + TypeConversionFunction::Target::kType)); } } // namespace cel