Skip to content

Commit 1ac4b1e

Browse files
zeitgeist87copybara-github
authored andcommitted
Add CEL enum formatting support
Introduces the `EnumValue`, which behaves like an `IntValue`, but keeps track of the enum name, so that it can be printed using `string.format()`. PiperOrigin-RevId: 717454914
1 parent 4fa3096 commit 1ac4b1e

File tree

9 files changed

+402
-27
lines changed

9 files changed

+402
-27
lines changed

common/value.cc

+41-13
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
namespace cel {
6464
namespace {
6565

66-
static constexpr std::array<ValueKind, 25> kValueToKindArray = {
66+
static constexpr std::array kValueToKindArray = {
6767
ValueKind::kError, ValueKind::kBool, ValueKind::kBytes,
6868
ValueKind::kDouble, ValueKind::kDuration, ValueKind::kError,
6969
ValueKind::kInt, ValueKind::kList, ValueKind::kList,
@@ -72,7 +72,7 @@ static constexpr std::array<ValueKind, 25> kValueToKindArray = {
7272
ValueKind::kNull, ValueKind::kOpaque, ValueKind::kString,
7373
ValueKind::kStruct, ValueKind::kStruct, ValueKind::kStruct,
7474
ValueKind::kTimestamp, ValueKind::kType, ValueKind::kUint,
75-
ValueKind::kUnknown};
75+
ValueKind::kUnknown, ValueKind::kInt};
7676

7777
static_assert(kValueToKindArray.size() ==
7878
absl::variant_size<common_internal::ValueVariant>(),
@@ -750,17 +750,20 @@ namespace {
750750
Value NonNullEnumValue(
751751
absl::Nonnull<const google::protobuf::EnumValueDescriptor*> value) {
752752
ABSL_DCHECK(value != nullptr);
753-
return IntValue(value->number());
753+
return EnumValue(value);
754754
}
755755

756756
Value NonNullEnumValue(absl::Nonnull<const google::protobuf::EnumDescriptor*> type,
757757
int32_t number) {
758758
ABSL_DCHECK(type != nullptr);
759-
if (type->is_closed()) {
760-
if (ABSL_PREDICT_FALSE(type->FindValueByNumber(number) == nullptr)) {
761-
return ErrorValue(absl::InvalidArgumentError(absl::StrCat(
762-
"closed enum has no such value: ", type->full_name(), ".", number)));
763-
}
759+
const google::protobuf::EnumValueDescriptor* enum_value =
760+
type->FindValueByNumber(number);
761+
if (type->is_closed() && ABSL_PREDICT_FALSE(enum_value == nullptr)) {
762+
return ErrorValue(absl::InvalidArgumentError(absl::StrCat(
763+
"closed enum has no such value: ", type->full_name(), ".", number)));
764+
}
765+
if (enum_value != nullptr) {
766+
return EnumValue(enum_value);
764767
}
765768
return IntValue(number);
766769
}
@@ -1943,6 +1946,18 @@ absl::optional<IntValue> Value::AsInt() const {
19431946
alternative != nullptr) {
19441947
return *alternative;
19451948
}
1949+
if (const auto* alternative = absl::get_if<EnumValue>(&variant_);
1950+
alternative != nullptr) {
1951+
return IntValue(alternative->NativeValue());
1952+
}
1953+
return absl::nullopt;
1954+
}
1955+
1956+
absl::optional<EnumValue> Value::AsEnum() const {
1957+
if (const auto* alternative = absl::get_if<EnumValue>(&variant_);
1958+
alternative != nullptr) {
1959+
return *alternative;
1960+
}
19461961
return absl::nullopt;
19471962
}
19481963

@@ -2350,18 +2365,31 @@ ErrorValue Value::GetError() && {
23502365
return absl::get<ErrorValue>(std::move(variant_));
23512366
}
23522367

2353-
IntValue Value::GetInt() const {
2354-
ABSL_DCHECK(IsInt()) << *this;
2355-
return absl::get<IntValue>(variant_);
2356-
}
2357-
23582368
#ifdef ABSL_HAVE_EXCEPTIONS
23592369
#define CEL_VALUE_THROW_BAD_VARIANT_ACCESS() throw absl::bad_variant_access()
23602370
#else
23612371
#define CEL_VALUE_THROW_BAD_VARIANT_ACCESS() \
23622372
ABSL_LOG(FATAL) << absl::bad_variant_access().what() /* Crash OK */
23632373
#endif
23642374

2375+
IntValue Value::GetInt() const {
2376+
ABSL_DCHECK(IsInt()) << *this;
2377+
if (const auto* alternative = absl::get_if<IntValue>(&variant_);
2378+
alternative != nullptr) {
2379+
return *alternative;
2380+
}
2381+
if (const auto* alternative = absl::get_if<EnumValue>(&variant_);
2382+
alternative != nullptr) {
2383+
return IntValue(alternative->NativeValue());
2384+
}
2385+
CEL_VALUE_THROW_BAD_VARIANT_ACCESS();
2386+
}
2387+
2388+
EnumValue Value::GetEnum() const {
2389+
ABSL_DCHECK(IsEnum()) << *this;
2390+
return absl::get<EnumValue>(variant_);
2391+
}
2392+
23652393
ListValue Value::GetList() const& {
23662394
ABSL_DCHECK(IsList()) << *this;
23672395
if (const auto* alternative =

common/value.h

+43-1
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,13 @@ class Value final {
501501
bool IsError() const { return absl::holds_alternative<ErrorValue>(variant_); }
502502

503503
// Returns `true` if this value is an instance of an int value.
504-
bool IsInt() const { return absl::holds_alternative<IntValue>(variant_); }
504+
bool IsInt() const {
505+
return absl::holds_alternative<IntValue>(variant_) ||
506+
absl::holds_alternative<EnumValue>(variant_);
507+
}
508+
509+
// Returns `true` if this value is an instance of an enum value.
510+
bool IsEnum() const { return absl::holds_alternative<EnumValue>(variant_); }
505511

506512
// Returns `true` if this value is an instance of a list value.
507513
bool IsList() const {
@@ -671,6 +677,13 @@ class Value final {
671677
return IsInt();
672678
}
673679

680+
// Convenience method for use with template metaprogramming. See
681+
// `IsEnum()`.
682+
template <typename T>
683+
std::enable_if_t<std::is_same_v<EnumValue, T>, bool> Is() const {
684+
return IsEnum();
685+
}
686+
674687
// Convenience method for use with template metaprogramming. See
675688
// `IsList()`.
676689
template <typename T>
@@ -856,6 +869,11 @@ class Value final {
856869
// int value. Otherwise an empty optional is returned.
857870
absl::optional<IntValue> AsInt() const;
858871

872+
// Performs a checked cast from a value to an enum value,
873+
// returning a non-empty optional with either a value or reference to the
874+
// enum value. Otherwise an empty optional is returned.
875+
absl::optional<EnumValue> AsEnum() const;
876+
859877
// Performs a checked cast from a value to a list value,
860878
// returning a non-empty optional with either a value or reference to the
861879
// list value. Otherwise an empty optional is returned.
@@ -1791,6 +1809,11 @@ class Value final {
17911809
// false, calling this method is undefined behavior.
17921810
IntValue GetInt() const;
17931811

1812+
// Performs an unchecked cast from a value to an int value. In
1813+
// debug builds a best effort is made to crash. If `IsInt()` would return
1814+
// false, calling this method is undefined behavior.
1815+
EnumValue GetEnum() const;
1816+
17941817
// Performs an unchecked cast from a value to a list value. In
17951818
// debug builds a best effort is made to crash. If `IsList()` would return
17961819
// false, calling this method is undefined behavior.
@@ -2104,6 +2127,25 @@ class Value final {
21042127
return GetInt();
21052128
}
21062129

2130+
// Convenience method for use with template metaprogramming. See
2131+
// `GetEnum()`.
2132+
template <typename T>
2133+
std::enable_if_t<std::is_same_v<EnumValue, T>, EnumValue> Get() & {
2134+
return GetEnum();
2135+
}
2136+
template <typename T>
2137+
std::enable_if_t<std::is_same_v<EnumValue, T>, EnumValue> Get() const& {
2138+
return GetEnum();
2139+
}
2140+
template <typename T>
2141+
std::enable_if_t<std::is_same_v<EnumValue, T>, EnumValue> Get() && {
2142+
return GetEnum();
2143+
}
2144+
template <typename T>
2145+
std::enable_if_t<std::is_same_v<EnumValue, T>, EnumValue> Get() const&& {
2146+
return GetEnum();
2147+
}
2148+
21072149
// Convenience method for use with template metaprogramming. See
21082150
// `GetList()`.
21092151
template <typename T>

common/values/enum_value.cc

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/protobuf/wrappers.pb.h"
16+
#include "absl/status/statusor.h"
17+
#include "common/value.h"
18+
19+
namespace cel {
20+
21+
absl::StatusOr<Value> EnumValue::Equal(ValueManager& value_manager,
22+
const Value& other) const {
23+
return IntValue(NativeValue()).Equal(value_manager, other);
24+
}
25+
26+
} // namespace cel

common/values/enum_value.h

+110-2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,27 @@
1818
#ifndef THIRD_PARTY_CEL_CPP_COMMON_VALUES_ENUM_VALUE_H_
1919
#define THIRD_PARTY_CEL_CPP_COMMON_VALUES_ENUM_VALUE_H_
2020

21+
#include <cstdint>
22+
#include <ostream>
23+
#include <string>
2124
#include <type_traits>
2225

2326
#include "google/protobuf/struct.pb.h"
27+
#include "absl/base/nullability.h"
2428
#include "absl/meta/type_traits.h"
29+
#include "absl/status/status.h"
30+
#include "absl/status/statusor.h"
31+
#include "absl/strings/cord.h"
32+
#include "absl/strings/string_view.h"
33+
#include "common/type.h"
34+
#include "common/value_kind.h"
35+
#include "common/values/int_value.h"
36+
#include "google/protobuf/descriptor.h"
2537
#include "google/protobuf/generated_enum_util.h"
38+
#include "google/protobuf/message.h"
2639

27-
namespace cel::common_internal {
40+
namespace cel {
41+
namespace common_internal {
2842

2943
template <typename T, typename U = absl::remove_cv_t<T>>
3044
inline constexpr bool kIsWellKnownEnumType =
@@ -44,6 +58,100 @@ using EnableIfGeneratedEnum = std::enable_if_t<
4458
absl::negation<std::bool_constant<kIsWellKnownEnumType<T>>>>::value,
4559
R>;
4660

47-
} // namespace cel::common_internal
61+
} // namespace common_internal
62+
63+
class Value;
64+
class ValueManager;
65+
class IntValue;
66+
class TypeManager;
67+
class EnumValue;
68+
69+
// `EnumValue` represents protobuf enum values which behave like values of the
70+
// primitive `int` type, except that they return the enum name in
71+
// `DebugString()`.
72+
class EnumValue final {
73+
public:
74+
static constexpr ValueKind kKind = ValueKind::kInt;
75+
76+
explicit EnumValue(
77+
absl::Nonnull<const google::protobuf::EnumValueDescriptor*> value) noexcept
78+
: value_(value->number()), name_(value->name()) {}
79+
explicit EnumValue(absl::string_view name, int64_t value) noexcept
80+
: value_(value), name_(name) {}
81+
82+
EnumValue(const EnumValue&) = default;
83+
EnumValue(EnumValue&&) = default;
84+
EnumValue& operator=(const EnumValue&) = default;
85+
EnumValue& operator=(EnumValue&&) = default;
86+
87+
ValueKind kind() const { return kKind; }
88+
89+
absl::string_view GetTypeName() const { return IntType::kName; }
90+
91+
absl::string_view GetEnumName() const { return name_; }
92+
93+
std::string DebugString() const { return std::string(GetEnumName()); }
94+
95+
// See Value::SerializeTo().
96+
absl::Status SerializeTo(
97+
absl::Nonnull<const google::protobuf::DescriptorPool*> descriptor_pool,
98+
absl::Nonnull<google::protobuf::MessageFactory*> message_factory,
99+
absl::Cord& value) const {
100+
return IntValue(NativeValue())
101+
.SerializeTo(descriptor_pool, message_factory, value);
102+
}
103+
104+
// See Value::ConvertToJson().
105+
absl::Status ConvertToJson(
106+
absl::Nonnull<const google::protobuf::DescriptorPool*> descriptor_pool,
107+
absl::Nonnull<google::protobuf::MessageFactory*> message_factory,
108+
absl::Nonnull<google::protobuf::Message*> json) const {
109+
return IntValue(NativeValue())
110+
.ConvertToJson(descriptor_pool, message_factory, json);
111+
}
112+
113+
absl::Status Equal(ValueManager& value_manager, const Value& other,
114+
Value& result) const {
115+
return IntValue(NativeValue()).Equal(value_manager, other, result);
116+
}
117+
absl::StatusOr<Value> Equal(ValueManager& value_manager,
118+
const Value& other) const;
119+
120+
bool IsZeroValue() const { return NativeValue() == 0; }
121+
122+
int64_t NativeValue() const { return static_cast<int64_t>(*this); }
123+
124+
// NOLINTNEXTLINE(google-explicit-constructor)
125+
operator int64_t() const noexcept { return value_; }
126+
127+
friend void swap(EnumValue& lhs, EnumValue& rhs) noexcept {
128+
using std::swap;
129+
swap(lhs.value_, rhs.value_);
130+
swap(lhs.name_, rhs.name_);
131+
}
132+
133+
private:
134+
int64_t value_;
135+
absl::string_view name_;
136+
};
137+
138+
template <typename H>
139+
H AbslHashValue(H state, EnumValue value) {
140+
return H::combine(std::move(state), value.NativeValue());
141+
}
142+
143+
inline bool operator==(EnumValue lhs, EnumValue rhs) {
144+
return lhs.NativeValue() == rhs.NativeValue();
145+
}
146+
147+
inline bool operator!=(EnumValue lhs, EnumValue rhs) {
148+
return !operator==(lhs, rhs);
149+
}
150+
151+
inline std::ostream& operator<<(std::ostream& out, EnumValue value) {
152+
return out << value.DebugString();
153+
}
154+
155+
} // namespace cel
48156

49157
#endif // THIRD_PARTY_CEL_CPP_COMMON_VALUES_ENUM_VALUE_H_

common/values/values.h

+12-8
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class ParsedMapFieldValue;
5555
class ParsedRepeatedFieldValue;
5656
class ParsedJsonListValue;
5757
class ParsedJsonMapValue;
58+
class EnumValue;
5859

5960
class CustomListValue;
6061
class CustomListValueInterface;
@@ -165,18 +166,21 @@ struct IsValueAlternative
165166
std::is_same<NullValue, T>, std::is_base_of<OpaqueValue, T>,
166167
std::is_same<StringValue, T>, IsStructValueAlternative<T>,
167168
std::is_same<TimestampValue, T>, std::is_same<TypeValue, T>,
168-
std::is_same<UintValue, T>, std::is_same<UnknownValue, T>>> {};
169+
std::is_same<UintValue, T>, std::is_same<UnknownValue, T>,
170+
std::is_same<EnumValue, T>>> {};
169171

170172
template <typename T>
171173
inline constexpr bool IsValueAlternativeV = IsValueAlternative<T>::value;
172174

173-
using ValueVariant = absl::variant<
174-
absl::monostate, BoolValue, BytesValue, DoubleValue, DurationValue,
175-
ErrorValue, IntValue, LegacyListValue, CustomListValue,
176-
ParsedRepeatedFieldValue, ParsedJsonListValue, LegacyMapValue,
177-
CustomMapValue, ParsedMapFieldValue, ParsedJsonMapValue, NullValue,
178-
OpaqueValue, StringValue, LegacyStructValue, CustomStructValue,
179-
ParsedMessageValue, TimestampValue, TypeValue, UintValue, UnknownValue>;
175+
using ValueVariant =
176+
absl::variant<absl::monostate, BoolValue, BytesValue, DoubleValue,
177+
DurationValue, ErrorValue, IntValue, LegacyListValue,
178+
CustomListValue, ParsedRepeatedFieldValue,
179+
ParsedJsonListValue, LegacyMapValue, CustomMapValue,
180+
ParsedMapFieldValue, ParsedJsonMapValue, NullValue,
181+
OpaqueValue, StringValue, LegacyStructValue,
182+
CustomStructValue, ParsedMessageValue, TimestampValue,
183+
TypeValue, UintValue, UnknownValue, EnumValue>;
180184

181185
// Get the base type alternative for the given alternative or interface. The
182186
// base type alternative is the type stored in the `ValueVariant`.

eval/compiler/resolver.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
namespace google::api::expr::runtime {
4040

41-
using ::cel::IntValue;
41+
using ::cel::EnumValue;
4242
using ::cel::TypeValue;
4343
using ::cel::Value;
4444

@@ -84,7 +84,7 @@ Resolver::Resolver(
8484
for (const auto& enumerator : enum_type.enumerators) {
8585
auto key = absl::StrCat(remainder, !remainder.empty() ? "." : "",
8686
enumerator.name);
87-
enum_value_map_[key] = IntValue(enumerator.number);
87+
enum_value_map_[key] = EnumValue(enumerator.name, enumerator.number);
8888
}
8989
}
9090
}

0 commit comments

Comments
 (0)