Implement v0.6 enum definitions

This does not implement accessors, just the enum definitions themselves.

PiperOrigin-RevId: 592591476
pull/15117/head
Alyssa Haroldsen 1 year ago committed by Copybara-Service
parent 35b564845c
commit 0ce51da377
  1. 1
      rust/BUILD
  2. 35
      rust/cpp.rs
  3. 51
      rust/enum.rs
  4. 1
      rust/internal.rs
  5. 3
      rust/shared.rs
  6. 27
      rust/test/BUILD
  7. 61
      rust/test/enums.proto
  8. 36
      rust/test/shared/BUILD
  9. 213
      rust/test/shared/enum_test.rs
  10. 30
      rust/upb.rs
  11. 30
      src/google/protobuf/compiler/rust/BUILD.bazel
  12. 365
      src/google/protobuf/compiler/rust/enum.cc
  13. 56
      src/google/protobuf/compiler/rust/enum.h
  14. 84
      src/google/protobuf/compiler/rust/enum_test.cc
  15. 7
      src/google/protobuf/compiler/rust/generator.cc
  16. 12
      src/google/protobuf/compiler/rust/message.cc

@ -47,6 +47,7 @@ rust_library(
#
# shared.rs is the root of the crate and has public items re-exported in protobuf.rs for user use.
PROTOBUF_SHARED = [
"enum.rs",
"internal.rs",
"macros.rs",
"optional.rs",

@ -8,13 +8,16 @@
// Rust Protobuf runtime using the C++ kernel.
use crate::ProtoStr;
use crate::__internal::{Private, PtrAndLen, RawArena, RawMap, RawMessage, RawRepeatedField};
use crate::{Mut, Proxied, ProxiedInRepeated, Repeated, SettableValue, View};
use crate::__internal::{Enum, Private, PtrAndLen, RawArena, RawMap, RawMessage, RawRepeatedField};
use crate::{
Mut, Proxied, ProxiedInRepeated, Repeated, RepeatedMut, RepeatedView, SettableValue, View,
};
use core::fmt::Debug;
use paste::paste;
use std::alloc::Layout;
use std::cell::UnsafeCell;
use std::convert::identity;
use std::ffi::c_int;
use std::fmt;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
@ -296,6 +299,34 @@ macro_rules! impl_repeated_primitives {
impl_repeated_primitives!(i32, u32, i64, u64, f32, f64, bool);
/// Cast a `RepeatedView<SomeEnum>` to `RepeatedView<c_int>`.
pub fn cast_enum_repeated_view<E: Enum + ProxiedInRepeated>(
private: Private,
repeated: RepeatedView<E>,
) -> RepeatedView<c_int> {
// SAFETY: the implementer of `Enum` has promised that this
// raw repeated is a type-erased `proto2::RepeatedField<int>*`.
unsafe { RepeatedView::from_raw(private, repeated.as_raw(Private)) }
}
/// Cast a `RepeatedMut<SomeEnum>` to `RepeatedMut<c_int>`.
///
/// Writing an unknown value is sound because all enums
/// are representationally open.
pub fn cast_enum_repeated_mut<E: Enum + ProxiedInRepeated>(
private: Private,
mut repeated: RepeatedMut<E>,
) -> RepeatedMut<i32> {
// SAFETY: the implementer of `Enum` has promised that this
// raw repeated is a type-erased `proto2::RepeatedField<int>*`.
unsafe {
RepeatedMut::from_inner(
private,
InnerRepeatedMut { raw: repeated.as_raw(Private), _phantom: PhantomData },
)
}
}
#[derive(Debug)]
pub struct MapInner<'msg, K: ?Sized, V: ?Sized> {
pub raw: RawMap,

@ -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> {}

@ -9,6 +9,7 @@
//! exposed to through the `protobuf` path but must be public for use by
//! generated code.
pub use crate::r#enum::Enum;
pub use crate::vtable::{
new_vtable_field_entry, BytesMutVTable, BytesOptionalMutVTable, PrimitiveOptionalMutVTable,
PrimitiveVTable, PrimitiveWithRawVTable, RawVTableMutator, RawVTableOptionalMutatorData,

@ -22,6 +22,7 @@ use std::fmt;
/// These are the items protobuf users can access directly.
#[doc(hidden)]
pub mod __public {
pub use crate::r#enum::UnknownEnumValue;
pub use crate::map::{Map, MapMut, MapView};
pub use crate::optional::{AbsentField, FieldEntry, Optional, PresentField};
pub use crate::primitive::PrimitiveMut;
@ -49,6 +50,8 @@ pub mod __runtime;
#[path = "upb.rs"]
pub mod __runtime;
#[path = "enum.rs"]
mod r#enum;
mod macros;
mod map;
mod optional;

@ -200,6 +200,33 @@ rust_upb_proto_library(
deps = [":edition2023_proto"],
)
proto_library(
name = "enums_proto",
testonly = True,
srcs = ["enums.proto"],
deps = ["//devtools/staticanalysis/pipeline/analyzers/proto_best_practices/proto:optouts_proto"],
)
cc_proto_library(
name = "enums_cc_proto",
testonly = True,
deps = [":enums_proto"],
)
rust_cc_proto_library(
name = "enums_cc_rust_proto",
testonly = True,
visibility = ["//rust/test/shared:__subpackages__"],
deps = [":enums_cc_proto"],
)
rust_upb_proto_library(
name = "enums_upb_rust_proto",
testonly = True,
visibility = ["//rust/test/shared:__subpackages__"],
deps = [":enums_proto"],
)
proto_library(
name = "no_package_import_proto",
testonly = True,

@ -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;
}

@ -94,6 +94,42 @@ rust_test(
],
)
rust_test(
name = "enum_cpp_test",
srcs = ["enum_test.rs"],
aliases = {
"//rust:protobuf_cpp": "protobuf",
},
tags = [
# TODO: Enable testing on arm once we support sanitizers for Rust on Arm.
"not_build:arm",
],
deps = [
"@crate_index//:googletest",
"//rust:protobuf_cpp",
"//rust/test:enums_cc_rust_proto",
"//rust/test:unittest_cc_rust_proto",
],
)
rust_test(
name = "enum_upb_test",
srcs = ["enum_test.rs"],
aliases = {
"//rust:protobuf_upb": "protobuf",
},
tags = [
# TODO: Enable testing on arm once we support sanitizers for Rust on Arm.
"not_build:arm",
],
deps = [
"@crate_index//:googletest",
"//rust:protobuf_upb",
"//rust/test:enums_upb_rust_proto",
"//rust/test:unittest_upb_rust_proto",
],
)
rust_test(
name = "package_cpp_test",
srcs = ["package_test.rs"],

@ -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;
}

@ -7,7 +7,7 @@
//! UPB FFI wrapper code for use by Rust Protobuf.
use crate::__internal::{Private, PtrAndLen, RawArena, RawMap, RawMessage, RawRepeatedField};
use crate::__internal::{Enum, Private, PtrAndLen, RawArena, RawMap, RawMessage, RawRepeatedField};
use crate::{
Mut, ProtoStr, Proxied, ProxiedInRepeated, Repeated, RepeatedMut, RepeatedView, SettableValue,
View, ViewProxy,
@ -423,7 +423,7 @@ macro_rules! impl_repeated_primitives {
unsafe { upb_Array_Get(f.as_raw(Private), i).$ufield }
}
unsafe fn repeated_set_unchecked(mut f: Mut<Repeated<$t>>, i: usize, v: View<$t>) {
unsafe { upb_Array_Set(f.as_raw(Private), i, upb_MessageValue { $ufield: v }) }
unsafe { upb_Array_Set(f.as_raw(Private), i, upb_MessageValue { $ufield: v.into() }) }
}
fn repeated_copy_from(src: View<Repeated<$t>>, mut dest: Mut<Repeated<$t>>) {
// SAFETY:
@ -463,6 +463,32 @@ impl_repeated_primitives!(
(u64, uint64_val, UpbCType::UInt64),
);
/// Cast a `RepeatedView<SomeEnum>` to `RepeatedView<i32>`.
pub fn cast_enum_repeated_view<E: Enum + ProxiedInRepeated>(
private: Private,
repeated: RepeatedView<E>,
) -> RepeatedView<i32> {
// SAFETY: Reading an enum array as an i32 array is sound.
unsafe { RepeatedView::from_raw(private, repeated.as_raw(Private)) }
}
/// Cast a `RepeatedMut<SomeEnum>` to `RepeatedMut<i32>`.
///
/// Writing an unknown value is sound because all enums
/// are representationally open.
pub fn cast_enum_repeated_mut<E: Enum + ProxiedInRepeated>(
private: Private,
repeated: RepeatedMut<E>,
) -> RepeatedMut<i32> {
// SAFETY:
// - Reading an enum array as an i32 array is sound.
// - No shared mutation is possible through the output.
unsafe {
let InnerRepeatedMut { arena, raw, .. } = repeated.into_inner();
RepeatedMut::from_inner(private, InnerRepeatedMut { arena, raw, _phantom: PhantomData })
}
}
/// Returns a static empty RepeatedView.
pub fn empty_array<T: ?Sized + ProxiedInRepeated>() -> RepeatedView<'static, T> {
// TODO: Consider creating a static empty array in C.

@ -17,6 +17,7 @@ cc_library(
],
deps = [
":context",
":enum",
":message",
":naming",
":relative_path",
@ -38,6 +39,7 @@ cc_library(
deps = [
":accessors",
":context",
":enum",
":naming",
":oneof",
"//src/google/protobuf:protobuf_nowkt",
@ -93,6 +95,34 @@ cc_library(
],
)
cc_library(
name = "enum",
srcs = ["enum.cc"],
hdrs = ["enum.h"],
copts = COPTS,
strip_include_prefix = "/src",
deps = [
":context",
"//src/google/protobuf:protobuf_nowkt",
"//src/google/protobuf/compiler/cpp:names_internal",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/log:absl_check",
"@com_google_absl//absl/strings",
],
)
cc_test(
name = "enum_test",
srcs = ["enum_test.cc"],
deps = [
":enum",
"@com_google_absl//absl/strings",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "naming",
srcs = ["naming.cc"],

@ -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

@ -23,6 +23,7 @@
#include "google/protobuf/compiler/code_generator.h"
#include "google/protobuf/compiler/cpp/names.h"
#include "google/protobuf/compiler/rust/context.h"
#include "google/protobuf/compiler/rust/enum.h"
#include "google/protobuf/compiler/rust/message.h"
#include "google/protobuf/compiler/rust/naming.h"
#include "google/protobuf/compiler/rust/relative_path.h"
@ -281,6 +282,12 @@ bool RustGenerator::Generate(const FileDescriptor* file,
thunks_ctx.printer().PrintRaw("\n");
}
}
for (int i = 0; i < file->enum_type_count(); ++i) {
GenerateEnumDefinition(ctx, *file->enum_type(i));
ctx.printer().PrintRaw("\n");
}
if (file == files_in_current_crate.front()) {
EmitClosingOfPackageModules(ctx, file->package());
}

@ -15,6 +15,7 @@
#include "google/protobuf/compiler/cpp/names.h"
#include "google/protobuf/compiler/rust/accessors/accessors.h"
#include "google/protobuf/compiler/rust/context.h"
#include "google/protobuf/compiler/rust/enum.h"
#include "google/protobuf/compiler/rust/naming.h"
#include "google/protobuf/compiler/rust/oneof.h"
#include "google/protobuf/descriptor.h"
@ -341,7 +342,7 @@ void GenerateRs(Context& ctx, const Descriptor& msg) {
ctx.printer().PrintRaw("\n");
}
}},
{"nested_msgs",
{"nested_in_msg",
[&] {
// If we have no nested types or oneofs, bail out without
// emitting an empty mod SomeMsg_.
@ -357,6 +358,12 @@ void GenerateRs(Context& ctx, const Descriptor& msg) {
GenerateRs(ctx, *msg.nested_type(i));
}
}},
{"nested_enums",
[&] {
for (int i = 0; i < msg.enum_type_count(); ++i) {
GenerateEnumDefinition(ctx, *msg.enum_type(i));
}
}},
{"oneofs",
[&] {
for (int i = 0; i < msg.real_oneof_decl_count(); ++i) {
@ -367,6 +374,7 @@ void GenerateRs(Context& ctx, const Descriptor& msg) {
#[allow(non_snake_case)]
pub mod $Msg$_ {
$nested_msgs$
$nested_enums$
$oneofs$
} // mod $Msg$_
@ -516,7 +524,7 @@ void GenerateRs(Context& ctx, const Descriptor& msg) {
$oneof_externs$
} // extern "C" for $Msg$
$nested_msgs$
$nested_in_msg$
)rs");
if (ctx.is_cpp()) {

Loading…
Cancel
Save