This does not implement accessors, just the enum definitions themselves. PiperOrigin-RevId: 592591476pull/15117/head
parent
35b564845c
commit
0ce51da377
16 changed files with 1006 additions and 6 deletions
@ -0,0 +1,51 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2023 Google LLC. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
use crate::__internal::Private; |
||||
use std::{ |
||||
error::Error, |
||||
fmt::{Debug, Display}, |
||||
marker::PhantomData, |
||||
}; |
||||
|
||||
/// Implemented by all generated enum types.
|
||||
///
|
||||
/// # Safety
|
||||
/// - A `RepeatedView<Self>` or `RepeatedMut<Self>` must have the same internal
|
||||
/// representation as erased enums in the runtime.
|
||||
/// - For C++, this is `proto2::RepeatedField<c_int>`
|
||||
/// - For UPB, this is an array compatible with `int32`
|
||||
pub unsafe trait Enum { |
||||
/// The name of the enum.
|
||||
const NAME: &'static str; |
||||
} |
||||
|
||||
/// An integer value wasn't known for an enum while converting.
|
||||
pub struct UnknownEnumValue<T>(i32, PhantomData<T>); |
||||
|
||||
impl<T> UnknownEnumValue<T> { |
||||
#[doc(hidden)] |
||||
pub fn new(_private: Private, unknown_value: i32) -> Self { |
||||
Self(unknown_value, PhantomData) |
||||
} |
||||
} |
||||
|
||||
impl<T> Debug for UnknownEnumValue<T> { |
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
||||
f.debug_tuple("UnknownEnumValue").field(&self.0).finish() |
||||
} |
||||
} |
||||
|
||||
impl<T: Enum> Display for UnknownEnumValue<T> { |
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
||||
let val = self.0; |
||||
let enum_name = T::NAME; |
||||
write!(f, "{val} is not a known value for {enum_name}") |
||||
} |
||||
} |
||||
|
||||
impl<T: Enum> Error for UnknownEnumValue<T> {} |
@ -0,0 +1,61 @@ |
||||
// Protocol Buffers - Google's data interchange format |
||||
// Copyright 2023 Google LLC. All rights reserved. |
||||
// |
||||
// Use of this source code is governed by a BSD-style |
||||
// license that can be found in the LICENSE file or at |
||||
// https://developers.google.com/open-source/licenses/bsd |
||||
// LINT: LEGACY_NAMES |
||||
|
||||
// The names in this file are meant to test edge cases. |
||||
syntax = "proto3"; |
||||
|
||||
package enums; |
||||
|
||||
import "devtools/staticanalysis/pipeline/analyzers/proto_best_practices/proto/optouts.proto"; |
||||
|
||||
option (proto_best_practices.file_optouts) = { |
||||
categories: ENUM_DEFAULT_VALUE_NAME_CONFLICT |
||||
categories: ENUM_VALUE_NAMES |
||||
}; |
||||
|
||||
// This should result in an enum with these accessible values: |
||||
// - Unknown = 0 |
||||
// - Unrecognized = 0 |
||||
// - Foo = 1 |
||||
// - Bar = 2 |
||||
// - DifferentNameAlias = 2 |
||||
enum TestEnumWithDuplicateStrippedPrefixNames { |
||||
option allow_alias = true; |
||||
|
||||
UNKNOWN = 0; |
||||
TestEnumWithDuplicateStrippedPrefixNamesUNRECOGNIZED = 0; |
||||
|
||||
TestEnumWithDuplicateStrippedPrefixNames_FOO = 1; |
||||
TEST_ENUM_WITH_DUPLICATE_STRIPPED_PREFIX_NAMES_FOO = 1; |
||||
FOO = 1; |
||||
|
||||
TestEnumWithDuplicateStrippedPrefixNamesBAR = 2; |
||||
TEST_ENUM_WITH_DUPLICATE_STRIPPED_PREFIX_NAMESBar = 2; |
||||
BAR = 2; |
||||
TEST_ENUM_WITH_DUPLICATE_STRIPPED_PREFIX_NAMES_DIFFERENT_NAME_ALIAS = 2; |
||||
} |
||||
|
||||
// This should result in an enum with these accessible values: |
||||
// - Unknown = 0 |
||||
// - _2020 = 1 |
||||
// - _2021 = 2 |
||||
// - _2022 = 3 |
||||
enum TestEnumWithNumericNames { |
||||
TestEnumWithNumericNamesUNKNOWN = 0; |
||||
TestEnumWithNumericNames_2020 = 1; |
||||
TEST_ENUM_WITH_NUMERIC_NAMES_2021 = 2; |
||||
TEST_ENUM_WITH_NUMERIC_NAMES2022 = 3; |
||||
} |
||||
|
||||
// This should result in an enum with these accessible values: |
||||
// - Unknown = 0 |
||||
// - TestEnumValueNameSameAsEnum = 1 |
||||
enum TestEnumValueNameSameAsEnum { |
||||
TEST_ENUM_VALUE_NAME_SAME_AS_ENUM_UNKNOWN = 0; |
||||
TEST_ENUM_VALUE_NAME_SAME_AS_ENUM = 1; |
||||
} |
@ -0,0 +1,213 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2023 Google LLC. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
//! Tests covering enum type generation.
|
||||
|
||||
use enums_proto::enums; |
||||
use googletest::prelude::*; |
||||
use unittest_proto::proto2_unittest; |
||||
|
||||
#[test] |
||||
fn test_nested_enum_values() { |
||||
assert_that!(i32::from(proto2_unittest::TestAllTypes_::NestedEnum::Foo), eq(1)); |
||||
assert_that!(i32::from(proto2_unittest::TestAllTypes_::NestedEnum::Bar), eq(2)); |
||||
assert_that!(i32::from(proto2_unittest::TestAllTypes_::NestedEnum::Baz), eq(3)); |
||||
assert_that!(i32::from(proto2_unittest::TestAllTypes_::NestedEnum::Neg), eq(-1)); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_enum_value_name_same_as_enum() { |
||||
assert_that!(i32::from(enums::TestEnumValueNameSameAsEnum::TestEnumValueNameSameAsEnum), eq(1)); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_enum_defaults() { |
||||
assert_that!( |
||||
proto2_unittest::TestSparseEnum::default(), |
||||
eq(proto2_unittest::TestSparseEnum::SparseA) |
||||
); |
||||
assert_that!( |
||||
proto2_unittest::TestEnumWithDupValue::default(), |
||||
eq(proto2_unittest::TestEnumWithDupValue::Foo1) |
||||
); |
||||
assert_that!( |
||||
proto2_unittest::TestEnumWithDupValue::default(), |
||||
eq(proto2_unittest::TestEnumWithDupValue::Foo2) |
||||
); |
||||
assert_that!( |
||||
enums::TestEnumWithDuplicateStrippedPrefixNames::default(), |
||||
eq(enums::TestEnumWithDuplicateStrippedPrefixNames::Unknown) |
||||
); |
||||
assert_that!( |
||||
proto2_unittest::TestAllTypes_::NestedEnum::default(), |
||||
eq(proto2_unittest::TestAllTypes_::NestedEnum::Foo) |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
#[deny(unreachable_patterns)] |
||||
#[allow(clippy::let_unit_value)] |
||||
fn test_closed_enum_is_nonexhaustive() { |
||||
let val = proto2_unittest::ForeignEnum::ForeignFoo; |
||||
let _it_compiles: () = match val { |
||||
proto2_unittest::ForeignEnum::ForeignFoo => (), |
||||
proto2_unittest::ForeignEnum::ForeignBar => (), |
||||
proto2_unittest::ForeignEnum::ForeignBaz => (), |
||||
proto2_unittest::ForeignEnum::ForeignBax => (), |
||||
_ => unreachable!(), |
||||
}; |
||||
} |
||||
|
||||
#[test] |
||||
fn test_closed_enum_conversion() { |
||||
assert_that!(i32::from(proto2_unittest::TestSparseEnum::SparseA), eq(123)); |
||||
assert_that!( |
||||
proto2_unittest::TestSparseEnum::try_from(123), |
||||
ok(eq(proto2_unittest::TestSparseEnum::SparseA)) |
||||
); |
||||
|
||||
assert_that!(i32::from(proto2_unittest::TestSparseEnum::SparseD), eq(-15)); |
||||
assert_that!( |
||||
proto2_unittest::TestSparseEnum::try_from(-15), |
||||
ok(eq(proto2_unittest::TestSparseEnum::SparseD)) |
||||
); |
||||
|
||||
assert_that!( |
||||
proto2_unittest::TestSparseEnum::try_from(0), |
||||
ok(eq(proto2_unittest::TestSparseEnum::SparseF)) |
||||
); |
||||
assert_that!(proto2_unittest::TestSparseEnum::try_from(1), err(anything())); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_closed_aliased_enum_conversion() { |
||||
assert_that!(i32::from(proto2_unittest::TestEnumWithDupValue::Foo1), eq(1)); |
||||
assert_that!(i32::from(proto2_unittest::TestEnumWithDupValue::Foo2), eq(1)); |
||||
assert_that!(i32::from(proto2_unittest::TestEnumWithDupValue::Bar1), eq(2)); |
||||
assert_that!(i32::from(proto2_unittest::TestEnumWithDupValue::Bar2), eq(2)); |
||||
assert_that!(i32::from(proto2_unittest::TestEnumWithDupValue::Baz), eq(3)); |
||||
|
||||
assert_that!( |
||||
proto2_unittest::TestEnumWithDupValue::try_from(1), |
||||
ok(eq(proto2_unittest::TestEnumWithDupValue::Foo1)) |
||||
); |
||||
assert_that!( |
||||
proto2_unittest::TestEnumWithDupValue::try_from(2), |
||||
ok(eq(proto2_unittest::TestEnumWithDupValue::Bar1)) |
||||
); |
||||
assert_that!( |
||||
proto2_unittest::TestEnumWithDupValue::try_from(3), |
||||
ok(eq(proto2_unittest::TestEnumWithDupValue::Baz)) |
||||
); |
||||
assert_that!(proto2_unittest::TestEnumWithDupValue::try_from(0), err(anything())); |
||||
assert_that!(proto2_unittest::TestEnumWithDupValue::try_from(4), err(anything())); |
||||
|
||||
assert_that!( |
||||
proto2_unittest::TestEnumWithDupValue::Foo1, |
||||
eq(proto2_unittest::TestEnumWithDupValue::Foo2) |
||||
); |
||||
assert_that!( |
||||
proto2_unittest::TestEnumWithDupValue::Bar1, |
||||
eq(proto2_unittest::TestEnumWithDupValue::Bar2) |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
#[deny(unreachable_patterns)] |
||||
#[allow(clippy::let_unit_value)] |
||||
fn test_open_enum_is_nonexhaustive() { |
||||
let val = enums::TestEnumValueNameSameAsEnum::Unknown; |
||||
let _it_compiles: () = match val { |
||||
enums::TestEnumValueNameSameAsEnum::Unknown => (), |
||||
enums::TestEnumValueNameSameAsEnum::TestEnumValueNameSameAsEnum => (), |
||||
_ => unreachable!(), |
||||
}; |
||||
} |
||||
|
||||
#[test] |
||||
fn test_open_enum_conversion() { |
||||
assert_that!(i32::from(enums::TestEnumWithNumericNames::Unknown), eq(0)); |
||||
assert_that!(i32::from(enums::TestEnumWithNumericNames::_2020), eq(1)); |
||||
assert_that!(i32::from(enums::TestEnumWithNumericNames::_2021), eq(2)); |
||||
assert_that!(i32::from(enums::TestEnumWithNumericNames::_2022), eq(3)); |
||||
|
||||
assert_that!( |
||||
enums::TestEnumWithNumericNames::from(0), |
||||
eq(enums::TestEnumWithNumericNames::Unknown) |
||||
); |
||||
assert_that!( |
||||
enums::TestEnumWithNumericNames::from(1), |
||||
eq(enums::TestEnumWithNumericNames::_2020) |
||||
); |
||||
assert_that!( |
||||
enums::TestEnumWithNumericNames::from(2), |
||||
eq(enums::TestEnumWithNumericNames::_2021) |
||||
); |
||||
assert_that!( |
||||
enums::TestEnumWithNumericNames::from(3), |
||||
eq(enums::TestEnumWithNumericNames::_2022) |
||||
); |
||||
assert_that!( |
||||
enums::TestEnumWithNumericNames::from(4), |
||||
not(any![ |
||||
eq(enums::TestEnumWithNumericNames::Unknown), |
||||
eq(enums::TestEnumWithNumericNames::_2020), |
||||
eq(enums::TestEnumWithNumericNames::_2021), |
||||
eq(enums::TestEnumWithNumericNames::_2022), |
||||
]) |
||||
); |
||||
assert_that!(i32::from(enums::TestEnumWithNumericNames::from(-1)), eq(-1)); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_open_aliased_enum_conversion() { |
||||
assert_that!(i32::from(enums::TestEnumWithDuplicateStrippedPrefixNames::Unknown), eq(0)); |
||||
assert_that!(i32::from(enums::TestEnumWithDuplicateStrippedPrefixNames::Foo), eq(1)); |
||||
assert_that!(i32::from(enums::TestEnumWithDuplicateStrippedPrefixNames::Bar), eq(2)); |
||||
assert_that!( |
||||
i32::from(enums::TestEnumWithDuplicateStrippedPrefixNames::DifferentNameAlias), |
||||
eq(2) |
||||
); |
||||
|
||||
assert_that!( |
||||
enums::TestEnumWithDuplicateStrippedPrefixNames::from(0), |
||||
eq(enums::TestEnumWithDuplicateStrippedPrefixNames::Unknown) |
||||
); |
||||
assert_that!( |
||||
enums::TestEnumWithDuplicateStrippedPrefixNames::from(1), |
||||
eq(enums::TestEnumWithDuplicateStrippedPrefixNames::Foo) |
||||
); |
||||
assert_that!( |
||||
enums::TestEnumWithDuplicateStrippedPrefixNames::from(2), |
||||
eq(enums::TestEnumWithDuplicateStrippedPrefixNames::Bar) |
||||
); |
||||
assert_that!( |
||||
enums::TestEnumWithDuplicateStrippedPrefixNames::from(2), |
||||
eq(enums::TestEnumWithDuplicateStrippedPrefixNames::DifferentNameAlias) |
||||
); |
||||
assert_that!( |
||||
enums::TestEnumWithDuplicateStrippedPrefixNames::from(3), |
||||
not(any![ |
||||
eq(enums::TestEnumWithDuplicateStrippedPrefixNames::Unknown), |
||||
eq(enums::TestEnumWithDuplicateStrippedPrefixNames::Foo), |
||||
eq(enums::TestEnumWithDuplicateStrippedPrefixNames::Bar), |
||||
]) |
||||
); |
||||
assert_that!(i32::from(enums::TestEnumWithDuplicateStrippedPrefixNames::from(5)), eq(5)); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_enum_conversion_failure_display() { |
||||
let err = proto2_unittest::TestSparseEnum::try_from(1).unwrap_err(); |
||||
assert_that!(format!("{err}"), eq("1 is not a known value for TestSparseEnum")); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_enum_conversion_failure_impls_std_error() { |
||||
let err = proto2_unittest::TestSparseEnum::try_from(1).unwrap_err(); |
||||
let _test_compiles: &dyn std::error::Error = &err; |
||||
} |
@ -0,0 +1,365 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2023 Google LLC. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#include "google/protobuf/compiler/rust/enum.h" |
||||
|
||||
#include <cstddef> |
||||
#include <cstdint> |
||||
#include <initializer_list> |
||||
#include <string> |
||||
#include <utility> |
||||
#include <vector> |
||||
|
||||
#include "absl/container/flat_hash_map.h" |
||||
#include "absl/container/flat_hash_set.h" |
||||
#include "absl/log/absl_check.h" |
||||
#include "absl/strings/ascii.h" |
||||
#include "absl/strings/match.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/str_join.h" |
||||
#include "absl/strings/string_view.h" |
||||
#include "absl/strings/strip.h" |
||||
#include "absl/types/span.h" |
||||
#include "google/protobuf/compiler/cpp/helpers.h" |
||||
#include "google/protobuf/compiler/rust/context.h" |
||||
#include "google/protobuf/descriptor.h" |
||||
|
||||
namespace google { |
||||
namespace protobuf { |
||||
namespace compiler { |
||||
namespace rust { |
||||
|
||||
namespace { |
||||
|
||||
std::string enumName(const EnumDescriptor& desc) { |
||||
return cpp::UnderscoresToCamelCase(desc.name(), /*cap first letter=*/true); |
||||
} |
||||
|
||||
// Constructs input for `EnumValues` from an enum descriptor.
|
||||
std::vector<std::pair<absl::string_view, int32_t>> enumValuesInput( |
||||
const EnumDescriptor& desc) { |
||||
std::vector<std::pair<absl::string_view, int32_t>> result; |
||||
result.reserve(static_cast<size_t>(desc.value_count())); |
||||
|
||||
for (int i = 0; i < desc.value_count(); ++i) { |
||||
result.emplace_back(desc.value(i)->name(), desc.value(i)->number()); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<RustEnumValue> EnumValues( |
||||
absl::string_view enum_name, |
||||
absl::Span<const std::pair<absl::string_view, int32_t>> values) { |
||||
// Enum values may have a prefix of the name of the enum stripped from the
|
||||
// value names in the gencode. This prefix is flexible:
|
||||
// - It can be the original enum name, the name as UpperCamel, or snake_case.
|
||||
// - The stripped prefix may also end in an underscore.
|
||||
|
||||
// The set of prefixes that will be stripped.
|
||||
std::initializer_list<std::string> prefixes = { |
||||
std::string(enum_name), |
||||
ScreamingSnakeToUpperCamelCase(enum_name), |
||||
CamelToSnakeCase(enum_name), |
||||
}; |
||||
|
||||
absl::flat_hash_set<std::string> seen_by_name; |
||||
absl::flat_hash_map<int32_t, RustEnumValue*> seen_by_number; |
||||
std::vector<RustEnumValue> result; |
||||
// The below code depends on pointer stability of elements in `result`;
|
||||
// this reserve must not be too low.
|
||||
result.reserve(values.size()); |
||||
seen_by_name.reserve(values.size()); |
||||
seen_by_number.reserve(values.size()); |
||||
|
||||
for (const auto& name_and_number : values) { |
||||
absl::string_view base_value_name = name_and_number.first; |
||||
for (absl::string_view prefix : prefixes) { |
||||
if (absl::StartsWithIgnoreCase(base_value_name, prefix)) { |
||||
base_value_name.remove_prefix(prefix.size()); |
||||
|
||||
// Also strip a joining underscore, if present.
|
||||
absl::ConsumePrefix(&base_value_name, "_"); |
||||
|
||||
// Only strip one prefix.
|
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (base_value_name.empty()) { |
||||
// The enum value name has a similar name to the enum - don't strip.
|
||||
base_value_name = name_and_number.first; |
||||
} |
||||
|
||||
int32_t number = name_and_number.second; |
||||
std::string rust_value_name = |
||||
ScreamingSnakeToUpperCamelCase(base_value_name); |
||||
|
||||
// Invalid identifiers are prefixed with `_`.
|
||||
if (absl::ascii_isdigit(rust_value_name[0])) { |
||||
rust_value_name = absl::StrCat("_", rust_value_name); |
||||
} |
||||
|
||||
if (seen_by_name.contains(rust_value_name)) { |
||||
// Don't add an alias with the same normalized name.
|
||||
continue; |
||||
} |
||||
|
||||
auto it_and_inserted = seen_by_number.try_emplace(number); |
||||
if (it_and_inserted.second) { |
||||
// This is the first value with this number; this name is canonical.
|
||||
result.push_back(RustEnumValue{rust_value_name, number}); |
||||
it_and_inserted.first->second = &result.back(); |
||||
} else { |
||||
// This number has been seen before; this name is an alias.
|
||||
it_and_inserted.first->second->aliases.push_back(rust_value_name); |
||||
} |
||||
|
||||
seen_by_name.insert(std::move(rust_value_name)); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
std::string CamelToSnakeCase(absl::string_view input) { |
||||
std::string result; |
||||
result.reserve(input.size() + 4); // No reallocation for 4 _
|
||||
bool is_first_character = true; |
||||
bool last_char_was_underscore = false; |
||||
for (const char c : input) { |
||||
if (!is_first_character && absl::ascii_isupper(c) && |
||||
!last_char_was_underscore) { |
||||
result += '_'; |
||||
} |
||||
last_char_was_underscore = c == '_'; |
||||
result += absl::ascii_tolower(c); |
||||
is_first_character = false; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
std::string ScreamingSnakeToUpperCamelCase(absl::string_view input) { |
||||
std::string result; |
||||
bool cap_next_letter = true; |
||||
for (const char c : input) { |
||||
if (absl::ascii_isalpha(c)) { |
||||
if (cap_next_letter) { |
||||
result += absl::ascii_toupper(c); |
||||
} else { |
||||
result += absl::ascii_tolower(c); |
||||
} |
||||
cap_next_letter = false; |
||||
} else if (absl::ascii_isdigit(c)) { |
||||
result += c; |
||||
cap_next_letter = true; |
||||
} else { |
||||
cap_next_letter = true; |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
void GenerateEnumDefinition(Context& ctx, const EnumDescriptor& desc) { |
||||
std::string name = enumName(desc); |
||||
ABSL_CHECK(desc.value_count() > 0); |
||||
std::vector<RustEnumValue> values = |
||||
EnumValues(desc.name(), enumValuesInput(desc)); |
||||
ABSL_CHECK(!values.empty()); |
||||
|
||||
ctx.Emit( |
||||
{ |
||||
{"name", name}, |
||||
{"variants", |
||||
[&] { |
||||
for (const auto& value : values) { |
||||
std::string number_str = absl::StrCat(value.number); |
||||
// TODO: Replace with open enum variants when stable
|
||||
ctx.Emit({{"variant_name", value.name}, {"number", number_str}}, |
||||
R"rs( |
||||
pub const $variant_name$: $name$ = $name$($number$); |
||||
)rs"); |
||||
for (const auto& alias : value.aliases) { |
||||
ctx.Emit({{"alias_name", alias}, {"number", number_str}}, |
||||
R"rs( |
||||
pub const $alias_name$: $name$ = $name$($number$); |
||||
)rs"); |
||||
} |
||||
} |
||||
}}, |
||||
// The default value of an enum is the first listed value.
|
||||
// The compiler checks that this is equal to 0 for open enums.
|
||||
{"default_int_value", absl::StrCat(desc.value(0)->number())}, |
||||
{"impl_from_i32", |
||||
[&] { |
||||
if (desc.is_closed()) { |
||||
ctx.Emit({{"name", name}, |
||||
{"known_values_pattern", |
||||
// TODO: Check validity in UPB/C++.
|
||||
absl::StrJoin( |
||||
values, "|", |
||||
[](std::string* o, const RustEnumValue& val) { |
||||
absl::StrAppend(o, val.number); |
||||
})}}, |
||||
R"rs( |
||||
impl $std$::convert::TryFrom<i32> for $name$ { |
||||
type Error = $pb$::UnknownEnumValue<Self>; |
||||
|
||||
fn try_from(val: i32) -> Result<$name$, Self::Error> { |
||||
if matches!(val, $known_values_pattern$) { |
||||
Ok(Self(val)) |
||||
} else { |
||||
Err($pb$::UnknownEnumValue::new($pbi$::Private, val)) |
||||
} |
||||
} |
||||
} |
||||
)rs"); |
||||
} else { |
||||
ctx.Emit({{"name", name}}, R"rs( |
||||
impl $std$::convert::From<i32> for $name$ { |
||||
fn from(val: i32) -> $name$ { |
||||
Self(val) |
||||
} |
||||
} |
||||
)rs"); |
||||
} |
||||
}}, |
||||
}, |
||||
R"rs( |
||||
#[repr(transparent)] |
||||
#[derive(Clone, Copy, PartialEq, Eq)] |
||||
pub struct $name$(i32); |
||||
|
||||
#[allow(non_upper_case_globals)] |
||||
impl $name$ { |
||||
$variants$ |
||||
} |
||||
|
||||
impl $std$::convert::From<$name$> for i32 { |
||||
fn from(val: $name$) -> i32 { |
||||
val.0 |
||||
} |
||||
} |
||||
|
||||
$impl_from_i32$ |
||||
|
||||
impl $std$::default::Default for $name$ { |
||||
fn default() -> Self { |
||||
Self($default_int_value$) |
||||
} |
||||
} |
||||
|
||||
impl $std$::fmt::Debug for $name$ { |
||||
fn fmt(&self, f: &mut $std$::fmt::Formatter<'_>) -> $std$::fmt::Result { |
||||
f.debug_tuple(stringify!($name$)).field(&self.0).finish() |
||||
} |
||||
} |
||||
|
||||
impl $pb$::Proxied for $name$ { |
||||
type View<'a> = $name$; |
||||
type Mut<'a> = $pb$::PrimitiveMut<'a, $name$>; |
||||
} |
||||
|
||||
impl $pb$::ViewProxy<'_> for $name$ { |
||||
type Proxied = $name$; |
||||
|
||||
fn as_view(&self) -> $name$ { |
||||
*self |
||||
} |
||||
|
||||
fn into_view<'shorter>(self) -> $pb$::View<'shorter, $name$> { |
||||
self |
||||
} |
||||
} |
||||
|
||||
impl $pb$::SettableValue<$name$> for $name$ { |
||||
fn set_on<'msg>( |
||||
self, |
||||
private: $pbi$::Private, |
||||
mut mutator: $pb$::Mut<'msg, $name$> |
||||
) where $name$: 'msg { |
||||
mutator.set_primitive(private, self) |
||||
} |
||||
} |
||||
|
||||
impl $pb$::ProxiedWithPresence for $name$ { |
||||
type PresentMutData<'a> = $pbi$::RawVTableOptionalMutatorData<'a, $name$>; |
||||
type AbsentMutData<'a> = $pbi$::RawVTableOptionalMutatorData<'a, $name$>; |
||||
|
||||
fn clear_present_field( |
||||
present_mutator: Self::PresentMutData<'_>, |
||||
) -> Self::AbsentMutData<'_> { |
||||
present_mutator.clear($pbi$::Private) |
||||
} |
||||
|
||||
fn set_absent_to_default( |
||||
absent_mutator: Self::AbsentMutData<'_>, |
||||
) -> Self::PresentMutData<'_> { |
||||
absent_mutator.set_absent_to_default($pbi$::Private) |
||||
} |
||||
} |
||||
|
||||
unsafe impl $pb$::ProxiedInRepeated for $name$ { |
||||
fn repeated_len(r: $pb$::View<$pb$::Repeated<Self>>) -> usize { |
||||
$pbr$::cast_enum_repeated_view($pbi$::Private, r).len() |
||||
} |
||||
|
||||
fn repeated_push(r: $pb$::Mut<$pb$::Repeated<Self>>, val: $name$) { |
||||
$pbr$::cast_enum_repeated_mut($pbi$::Private, r).push(val.into()) |
||||
} |
||||
|
||||
fn repeated_clear(r: $pb$::Mut<$pb$::Repeated<Self>>) { |
||||
$pbr$::cast_enum_repeated_mut($pbi$::Private, r).clear() |
||||
} |
||||
|
||||
unsafe fn repeated_get_unchecked( |
||||
r: $pb$::View<$pb$::Repeated<Self>>, |
||||
index: usize, |
||||
) -> $pb$::View<$name$> { |
||||
// SAFETY: In-bounds as promised by the caller.
|
||||
unsafe { |
||||
$pbr$::cast_enum_repeated_view($pbi$::Private, r) |
||||
.get_unchecked(index) |
||||
.try_into() |
||||
.unwrap_unchecked() |
||||
} |
||||
} |
||||
|
||||
unsafe fn repeated_set_unchecked( |
||||
r: $pb$::Mut<$pb$::Repeated<Self>>, |
||||
index: usize, |
||||
val: $name$, |
||||
) { |
||||
// SAFETY: In-bounds as promised by the caller.
|
||||
unsafe { |
||||
$pbr$::cast_enum_repeated_mut($pbi$::Private, r) |
||||
.set_unchecked(index, val.into()) |
||||
} |
||||
} |
||||
|
||||
fn repeated_copy_from( |
||||
src: $pb$::View<$pb$::Repeated<Self>>, |
||||
dest: $pb$::Mut<$pb$::Repeated<Self>>, |
||||
) { |
||||
$pbr$::cast_enum_repeated_mut($pbi$::Private, dest) |
||||
.copy_from($pbr$::cast_enum_repeated_view($pbi$::Private, src)) |
||||
} |
||||
} |
||||
|
||||
impl $pbi$::PrimitiveWithRawVTable for $name$ {} |
||||
|
||||
// SAFETY: this is an enum type
|
||||
unsafe impl $pbi$::Enum for $name$ { |
||||
const NAME: &'static str = "$name$"; |
||||
} |
||||
)rs"); |
||||
} |
||||
|
||||
} // namespace rust
|
||||
} // namespace compiler
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
@ -0,0 +1,56 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2023 Google LLC. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#ifndef GOOGLE_PROTOBUF_COMPILER_RUST_ENUM_H__ |
||||
#define GOOGLE_PROTOBUF_COMPILER_RUST_ENUM_H__ |
||||
|
||||
#include <cstdint> |
||||
#include <string> |
||||
#include <utility> |
||||
#include <vector> |
||||
|
||||
#include "absl/strings/string_view.h" |
||||
#include "absl/types/span.h" |
||||
#include "google/protobuf/compiler/rust/context.h" |
||||
#include "google/protobuf/descriptor.h" |
||||
|
||||
namespace google { |
||||
namespace protobuf { |
||||
namespace compiler { |
||||
namespace rust { |
||||
|
||||
void GenerateEnumDefinition(Context& ctx, const EnumDescriptor& desc); |
||||
|
||||
// An enum value with a unique number and any aliases for it.
|
||||
struct RustEnumValue { |
||||
// The canonical CamelCase name in Rust.
|
||||
std::string name; |
||||
int32_t number; |
||||
std::vector<std::string> aliases; |
||||
}; |
||||
|
||||
// Returns the list of rust enum variants to produce, along with their aliases.
|
||||
// Performs name normalization, deduplication, and alias determination.
|
||||
// The `number` and `name` of every returned `RustEnumValue` is unique.
|
||||
std::vector<RustEnumValue> EnumValues( |
||||
absl::string_view enum_name, |
||||
absl::Span<const std::pair<absl::string_view, int32_t>> values); |
||||
|
||||
// TODO: Unify these with other case-conversion functions.
|
||||
|
||||
// Converts an UpperCamel or lowerCamel string to a snake_case string.
|
||||
std::string CamelToSnakeCase(absl::string_view input); |
||||
|
||||
// Converts a SCREAMING_SNAKE_CASE string to an UpperCamelCase string.
|
||||
std::string ScreamingSnakeToUpperCamelCase(absl::string_view input); |
||||
|
||||
} // namespace rust
|
||||
} // namespace compiler
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
|
||||
#endif // GOOGLE_PROTOBUF_COMPILER_RUST_ENUM_H__
|
@ -0,0 +1,84 @@ |
||||
#include "google/protobuf/compiler/rust/enum.h" |
||||
|
||||
#include <cstdint> |
||||
|
||||
#include <gmock/gmock.h> |
||||
#include <gtest/gtest.h> |
||||
#include "absl/strings/string_view.h" |
||||
|
||||
namespace { |
||||
|
||||
using ::google::protobuf::compiler::rust::CamelToSnakeCase; |
||||
using ::google::protobuf::compiler::rust::EnumValues; |
||||
using ::google::protobuf::compiler::rust::RustEnumValue; |
||||
using ::google::protobuf::compiler::rust::ScreamingSnakeToUpperCamelCase; |
||||
using ::testing::AllOf; |
||||
using ::testing::ElementsAre; |
||||
using ::testing::Eq; |
||||
using ::testing::Field; |
||||
using ::testing::IsEmpty; |
||||
|
||||
TEST(EnumTest, CamelToSnakeCase) { |
||||
// TODO: Review this behavior.
|
||||
EXPECT_EQ(CamelToSnakeCase("CamelCase"), "camel_case"); |
||||
EXPECT_EQ(CamelToSnakeCase("_CamelCase"), "_camel_case"); |
||||
EXPECT_EQ(CamelToSnakeCase("camelCase"), "camel_case"); |
||||
EXPECT_EQ(CamelToSnakeCase("Number2020"), "number2020"); |
||||
EXPECT_EQ(CamelToSnakeCase("Number_2020"), "number_2020"); |
||||
EXPECT_EQ(CamelToSnakeCase("camelCase_"), "camel_case_"); |
||||
EXPECT_EQ(CamelToSnakeCase("CamelCaseTrio"), "camel_case_trio"); |
||||
EXPECT_EQ(CamelToSnakeCase("UnderIn_Middle"), "under_in_middle"); |
||||
EXPECT_EQ(CamelToSnakeCase("Camel_Case"), "camel_case"); |
||||
EXPECT_EQ(CamelToSnakeCase("Camel__Case"), "camel__case"); |
||||
EXPECT_EQ(CamelToSnakeCase("CAMEL_CASE"), "c_a_m_e_l_c_a_s_e"); |
||||
} |
||||
|
||||
TEST(EnumTest, ScreamingSnakeToUpperCamelCase) { |
||||
// TODO: Review this behavior.
|
||||
EXPECT_EQ(ScreamingSnakeToUpperCamelCase("CAMEL_CASE"), "CamelCase"); |
||||
EXPECT_EQ(ScreamingSnakeToUpperCamelCase("NUMBER2020"), "Number2020"); |
||||
EXPECT_EQ(ScreamingSnakeToUpperCamelCase("NUMBER_2020"), "Number2020"); |
||||
EXPECT_EQ(ScreamingSnakeToUpperCamelCase("FOO_4040_BAR"), "Foo4040Bar"); |
||||
EXPECT_EQ(ScreamingSnakeToUpperCamelCase("FOO_4040bar"), "Foo4040Bar"); |
||||
EXPECT_EQ(ScreamingSnakeToUpperCamelCase("_CAMEL_CASE"), "CamelCase"); |
||||
|
||||
// This function doesn't currently preserve prefix/suffix underscore,
|
||||
// while CamelToSnakeCase does.
|
||||
EXPECT_EQ(ScreamingSnakeToUpperCamelCase("CAMEL_CASE_"), "CamelCase"); |
||||
EXPECT_EQ(ScreamingSnakeToUpperCamelCase("camel_case"), "CamelCase"); |
||||
EXPECT_EQ(ScreamingSnakeToUpperCamelCase("CAMEL_CASE_TRIO"), "CamelCaseTrio"); |
||||
EXPECT_EQ(ScreamingSnakeToUpperCamelCase("UNDER_IN__MIDDLE"), |
||||
"UnderInMiddle"); |
||||
} |
||||
|
||||
template <class Aliases> |
||||
auto EnumValue(absl::string_view name, int32_t number, Aliases aliases) { |
||||
return AllOf(Field("name", &RustEnumValue::name, Eq(name)), |
||||
Field("number", &RustEnumValue::number, Eq(number)), |
||||
Field("aliases", &RustEnumValue::aliases, aliases)); |
||||
} |
||||
|
||||
auto EnumValue(absl::string_view name, int32_t number) { |
||||
return EnumValue(name, number, IsEmpty()); |
||||
} |
||||
|
||||
TEST(EnumTest, EnumValues) { |
||||
EXPECT_THAT(EnumValues("Enum", {{"ENUM_FOO", 1}, {"ENUM_BAR", 2}}), |
||||
ElementsAre(EnumValue("Foo", 1), EnumValue("Bar", 2))); |
||||
EXPECT_THAT(EnumValues("Enum", {{"FOO", 1}, {"ENUM_BAR", 2}}), |
||||
ElementsAre(EnumValue("Foo", 1), EnumValue("Bar", 2))); |
||||
EXPECT_THAT(EnumValues("Enum", {{"enumFOO", 1}, {"eNuM_BaR", 2}}), |
||||
ElementsAre(EnumValue("Foo", 1), EnumValue("Bar", 2))); |
||||
EXPECT_THAT(EnumValues("Enum", {{"ENUM_ENUM_UNKNOWN", 1}, {"ENUM_ENUM", 2}}), |
||||
ElementsAre(EnumValue("EnumUnknown", 1), EnumValue("Enum", 2))); |
||||
EXPECT_THAT(EnumValues("Enum", {{"ENUM_VAL", 1}, {"ENUM_ALIAS", 1}}), |
||||
ElementsAre(EnumValue("Val", 1, ElementsAre("Alias")))); |
||||
EXPECT_THAT( |
||||
EnumValues("Enum", |
||||
{{"ENUM_VAL", 1}, {"ENUM_ALIAS", 1}, {"ENUM_ALIAS2", 1}}), |
||||
ElementsAre(EnumValue("Val", 1, ElementsAre("Alias", "Alias2")))); |
||||
EXPECT_THAT(EnumValues("Enum", {{"ENUM_ENUM", 1}, {"ENUM", 1}}), |
||||
ElementsAre(EnumValue("Enum", 1, IsEmpty()))); |
||||
} |
||||
|
||||
} // namespace
|
Loading…
Reference in new issue