From 6bd7eb5926a3f868439438de18469442c093d1d4 Mon Sep 17 00:00:00 2001 From: Protobuf Team Bot Date: Fri, 28 Aug 2026 09:00:38 -0700 Subject: [PATCH] Expose generated per-file Rust proto modules as public. PiperOrigin-RevId: 972618969 --- .../protobuf/compiler/rust/generator.cc | 25 +++--- .../protobuf/compiler/rust/generator_test.cc | 90 ------------------- src/google/protobuf/compiler/rust/naming.cc | 30 +++---- src/google/protobuf/compiler/rust/naming.h | 2 +- .../protobuf/compiler/rust/naming_test.cc | 37 +++----- 5 files changed, 36 insertions(+), 148 deletions(-) diff --git a/src/google/protobuf/compiler/rust/generator.cc b/src/google/protobuf/compiler/rust/generator.cc index 7e7cdb53ff13c..b57459e3dc12f 100644 --- a/src/google/protobuf/compiler/rust/generator.cc +++ b/src/google/protobuf/compiler/rust/generator.cc @@ -132,23 +132,20 @@ void EmitEntryPointRsFile(GeneratorContext* generator_context, std::string non_primary_file_path = GetRsFile(ctx, *file); std::string relative_mod_path = primary_relpath.Relative(RelativePath(non_primary_file_path)); - // Expose each generated .proto file as a public module named after its - // (flattened) file path, providing a fully-qualified path to every type - // (e.g. `my_crate::google_network_api_proto::Config`). See - // RustModuleName for how the module name is derived from the path. - // - // The flat `pub use` re-export into the crate root is also emitted so that - // existing consumers relying on the crate-root namespace continue to work. - ctx.Emit( - {{"file_path", relative_mod_path}, {"mod_name", RustModuleName(*file)}}, - R"rs( + // Temporarily emit these re-exported mods as pub to avoid issues with + // Crubit. In a future change we should change these back to be private + // mods. + ctx.Emit({{"file_path", relative_mod_path}, + {"mod_name", RustInternalModuleName(*file)}}, + R"rs( #[path="$file_path$"] - #[allow(nonstandard_style, unused)] - pub mod $mod_name$; + #[allow(nonstandard_style, unused, unreachable_pub)] + #[doc(hidden)] + mod internal_do_not_use_$mod_name$; #[allow(nonstandard_style, unused)] #[doc(inline)] - pub use $mod_name$::*; + pub use internal_do_not_use_$mod_name$::*; )rs"); } @@ -217,7 +214,7 @@ bool RustGenerator::Generate(const FileDescriptor* file, &*import_path_to_crate_name); std::vector modules; - modules.emplace_back(RustModuleName(*file)); + modules.emplace_back(RustInternalModuleName(*file)); Context ctx_without_printer(&*opts, &rust_generator_context, nullptr, std::move(modules)); diff --git a/src/google/protobuf/compiler/rust/generator_test.cc b/src/google/protobuf/compiler/rust/generator_test.cc index 2ab34ef1fa3f1..cfd783148aaf1 100644 --- a/src/google/protobuf/compiler/rust/generator_test.cc +++ b/src/google/protobuf/compiler/rust/generator_test.cc @@ -204,96 +204,6 @@ TEST_F(RustGeneratorTest, EmitsEnumMetadata) { "Alias1", GeneratedCodeInfo::Annotation::NONE, 1); } -TEST_F(RustGeneratorTest, EmitsPublicFileModuleInEntryPoint) { - constexpr absl::string_view kFooProto = R"schema( - syntax = "proto2"; - package foo; - message Message { - })schema"; - CreateTempFile("foo.proto", kFooProto); - RunProtoc( - "protocol_compiler --proto_path=$tmpdir " - "--rust_out=$tmpdir " - "--rust_opt=experimental-codegen=enabled,kernel=cpp " - "foo.proto"); - ExpectNoErrors(); - - std::string entry_point = FileContents("generated.rs"); - EXPECT_THAT(entry_point, HasSubstr("pub mod foo_proto;")); - EXPECT_THAT(entry_point, HasSubstr("pub use foo_proto::*;")); - EXPECT_THAT(entry_point, Not(HasSubstr("internal_do_not_use_"))); - EXPECT_THAT(entry_point, Not(HasSubstr("#[doc(hidden)]"))); -} - -TEST_F(RustGeneratorTest, EmitsPublicFileModulesForMultiFileCrate) { - constexpr absl::string_view kFooProto = R"schema( - syntax = "proto2"; - package foo; - message Config { - })schema"; - constexpr absl::string_view kBarProto = R"schema( - syntax = "proto2"; - package bar; - message Config { - })schema"; - CreateTempFile("foo.proto", kFooProto); - CreateTempFile("bar.proto", kBarProto); - RunProtoc( - "protocol_compiler --proto_path=$tmpdir " - "--rust_out=$tmpdir " - "--rust_opt=experimental-codegen=enabled,kernel=cpp " - "foo.proto bar.proto"); - ExpectNoErrors(); - - std::string entry_point = FileContents("generated.rs"); - EXPECT_THAT(entry_point, HasSubstr("pub mod foo_proto;")); - EXPECT_THAT(entry_point, HasSubstr("pub mod bar_proto;")); - EXPECT_THAT(entry_point, Not(HasSubstr("internal_do_not_use_"))); -} - -TEST_F(RustGeneratorTest, EmitsValidIdentifierForLeadingDigitFile) { - constexpr absl::string_view kProto = R"schema( - syntax = "proto2"; - package sample; - message Config {} - )schema"; - CreateTempFile("0.1.proto", kProto); - RunProtoc( - "protocol_compiler --proto_path=$tmpdir " - "--rust_out=$tmpdir " - "--rust_opt=experimental-codegen=enabled,kernel=cpp " - "0.1.proto"); - ExpectNoErrors(); - - std::string entry_point = FileContents("generated.rs"); - // A leading non-letter gets a `pb_` prefix so the module name is a valid - // identifier (`0.1.proto` must not produce `pub mod 0_1_proto;`). - EXPECT_THAT(entry_point, HasSubstr("pub mod pb_0_1_proto;")); - EXPECT_THAT(entry_point, HasSubstr("pub use pb_0_1_proto::*;")); - EXPECT_THAT(entry_point, Not(HasSubstr("pub mod 0_1_proto;"))); -} - -TEST_F(RustGeneratorTest, EmitsReadableModuleNameForHyphenatedFile) { - constexpr absl::string_view kProto = R"schema( - syntax = "proto2"; - package sample; - message A {} - )schema"; - CreateTempFile("my-service.proto", kProto); - RunProtoc( - "protocol_compiler --proto_path=$tmpdir " - "--rust_out=$tmpdir " - "--rust_opt=experimental-codegen=enabled,kernel=cpp " - "my-service.proto"); - ExpectNoErrors(); - - std::string entry_point = FileContents("generated.rs"); - // Separators collapse to `_` and the `.proto` extension becomes `_proto`; - // no `_2d_` hex escaping is used for a plain hyphen. - EXPECT_THAT(entry_point, HasSubstr("pub mod my_service_proto;")); - EXPECT_THAT(entry_point, Not(HasSubstr("_2d_"))); -} - TEST_F(RustGeneratorTest, EmitsQualifiedPathForSameFileMessageReference) { // Foo uses Bar from the same file. constexpr absl::string_view kFooProto = R"schema( diff --git a/src/google/protobuf/compiler/rust/naming.cc b/src/google/protobuf/compiler/rust/naming.cc index 45cb4f7e35257..d6a838034a6a9 100644 --- a/src/google/protobuf/compiler/rust/naming.cc +++ b/src/google/protobuf/compiler/rust/naming.cc @@ -244,32 +244,22 @@ std::string RustModule(Context& ctx, const OneofDescriptor& oneof) { *oneof.file()); } -std::string RustModuleName(const FileDescriptor& file) { - // Derive a readable and (mostly) unique Rust module name from the full - // proto file path, e.g. `foo/bar/baz.proto` becomes `foo_bar_baz_proto`. - absl::string_view name = file.name(); - absl::string_view prefix = "pb_"; - +std::string RustInternalModuleName(const FileDescriptor& file) { std::string result; - result.reserve(name.size() + prefix.size()); - - // Rust identifiers must start with a letter or underscore. If the path begins - // with anything else (e.g. a digit), prepend `pb_` so the result is valid. - if (name.empty() || !absl::ascii_isalpha(name[0])) { - result += prefix; - } - - for (char c : name) { - // Common path/file separators (`/`, `-`, `.`, and `_`) all collapse to a - // single underscore for better readability. - if (c == '/' || c == '-' || c == '.' || c == '_') { - result += '_'; + result.reserve(file.name().size()); + for (char c : StripProto(file.name())) { + if (c == '_') { + result += "__"; + } else if (c == '-') { + result += "__"; + } else if (c == '/') { + result += "_s"; } else if (absl::ascii_isalnum(c)) { result += c; } else { // Escape any other characters that aren't valid in Rust identifiers // by substituting them with an underscore followed by their hex value - // and another underscore. + // and another underscore (e.g. '.' becomes '_2e_'). absl::StrAppendFormat(&result, "_%02x_", static_cast(c)); } } diff --git a/src/google/protobuf/compiler/rust/naming.h b/src/google/protobuf/compiler/rust/naming.h index 2af094902805a..d2a56969cdf52 100644 --- a/src/google/protobuf/compiler/rust/naming.h +++ b/src/google/protobuf/compiler/rust/naming.h @@ -106,7 +106,7 @@ std::string RustModule(Context& ctx, const Descriptor& msg); std::string RustModule(Context& ctx, const EnumDescriptor& enum_); std::string RustModule(Context& ctx, const OneofDescriptor& oneof); -std::string RustModuleName(const FileDescriptor& file); +std::string RustInternalModuleName(const FileDescriptor& file); template std::string GetUnderscoreDelimitedFullName(Context& ctx, const Desc& desc); diff --git a/src/google/protobuf/compiler/rust/naming_test.cc b/src/google/protobuf/compiler/rust/naming_test.cc index 3c4ce4ebab33e..bce7a9a06501f 100644 --- a/src/google/protobuf/compiler/rust/naming_test.cc +++ b/src/google/protobuf/compiler/rust/naming_test.cc @@ -9,41 +9,32 @@ #include "google/protobuf/io/zero_copy_stream_impl_lite.h" using google::protobuf::compiler::rust::CamelToSnakeCase; -using google::protobuf::compiler::rust::RustModuleName; +using google::protobuf::compiler::rust::RustInternalModuleName; using google::protobuf::compiler::rust::ScreamingSnakeToUpperCamelCase; namespace { -TEST(RustProtoNaming, RustModuleName) { +TEST(RustProtoNaming, RustInternalModuleName) { auto get_internal_module_name = [](const std::string& name) { google::protobuf::FileDescriptorProto file_proto; file_proto.set_name(name); google::protobuf::DescriptorPool pool; - return RustModuleName(*pool.BuildFile(file_proto)); + return RustInternalModuleName(*pool.BuildFile(file_proto)); }; EXPECT_EQ(get_internal_module_name("strong_bad/lol.proto"), - "strong_bad_lol_proto"); - EXPECT_EQ(get_internal_module_name("0.1.proto"), "pb_0_1_proto"); - EXPECT_EQ(get_internal_module_name("2fa.proto"), "pb_2fa_proto"); - EXPECT_EQ(get_internal_module_name("_.proto"), "pb___proto"); - EXPECT_EQ(get_internal_module_name("abc .proto"), "abc_20__20__20__proto"); - EXPECT_EQ(get_internal_module_name("hello (2).proto"), - "hello_20__28_2_29__proto"); - EXPECT_EQ(get_internal_module_name("k8s.min.proto"), "k8s_min_proto"); - EXPECT_EQ(get_internal_module_name("c++.proto"), "c_2b__2b__proto"); - EXPECT_EQ(get_internal_module_name("hello,world.proto"), - "hello_2c_world_proto"); + "strong__bad_slol"); + EXPECT_EQ(get_internal_module_name("0.1.proto"), "0_2e_1"); + EXPECT_EQ(get_internal_module_name("_.proto"), "__"); + EXPECT_EQ(get_internal_module_name("abc .proto"), "abc_20__20__20_"); + EXPECT_EQ(get_internal_module_name("hello (2).proto"), "hello_20__28_2_29_"); + EXPECT_EQ(get_internal_module_name("k8s.min.proto"), "k8s_2e_min"); + EXPECT_EQ(get_internal_module_name("c++.proto"), "c_2b__2b_"); + EXPECT_EQ(get_internal_module_name("hello,world.proto"), "hello_2c_world"); EXPECT_EQ(get_internal_module_name("hello..world.proto"), - "hello__world_proto"); + "hello_2e__2e_world"); EXPECT_EQ(get_internal_module_name("hello_你好.proto"), - "hello__e4__bd__a0__e5__a5__bd__proto"); - // Common separators (`/`, `-`, `.`, `_`) all collapse to a single underscore, - // so files differing only by these separators intentionally map to the same - // module name (any collision surfaces as a rustc E0428 error at build time). - EXPECT_EQ(get_internal_module_name("my-message.proto"), "my_message_proto"); - EXPECT_EQ(get_internal_module_name("my_message.proto"), "my_message_proto"); - EXPECT_EQ(get_internal_module_name("foo-bar.proto"), - get_internal_module_name("foo_bar.proto")); + "hello___e4__bd__a0__e5__a5__bd_"); + EXPECT_EQ(get_internal_module_name("my-message.proto"), "my__message"); } TEST(RustProtoNaming, CamelToSnakeCase) {