From 7143844db0055a8c760e6574f4a7b3693e2be6dd Mon Sep 17 00:00:00 2001 From: Protobuf Team Bot Date: Fri, 14 Apr 2023 13:06:17 -0700 Subject: [PATCH] Factor out common helpers in the Rust backend. This CL introduces two new files, names.h and context.h. The former is intended to hold functions that generate the stringified names of things to splat into text templates. The latter holds per-invocation options, and a Context struct that makes it easy to thread extra information throughout the codegen backend. PiperOrigin-RevId: 524366974 --- src/google/protobuf/compiler/rust/BUILD.bazel | 45 +- src/google/protobuf/compiler/rust/context.cc | 96 +++++ src/google/protobuf/compiler/rust/context.h | 112 +++++ .../protobuf/compiler/rust/generator.cc | 393 ++++++------------ src/google/protobuf/compiler/rust/generator.h | 1 + src/google/protobuf/compiler/rust/naming.cc | 130 ++++++ src/google/protobuf/compiler/rust/naming.h | 68 +++ 7 files changed, 569 insertions(+), 276 deletions(-) create mode 100644 src/google/protobuf/compiler/rust/context.cc create mode 100644 src/google/protobuf/compiler/rust/context.h create mode 100644 src/google/protobuf/compiler/rust/naming.cc create mode 100644 src/google/protobuf/compiler/rust/naming.h diff --git a/src/google/protobuf/compiler/rust/BUILD.bazel b/src/google/protobuf/compiler/rust/BUILD.bazel index 5d319d1023..a868d23a97 100644 --- a/src/google/protobuf/compiler/rust/BUILD.bazel +++ b/src/google/protobuf/compiler/rust/BUILD.bazel @@ -16,6 +16,8 @@ cc_library( "//src/google/protobuf/compiler:__pkg__", ], deps = [ + ":context", + ":naming", ":upb_kernel", "//src/google/protobuf:protobuf_nowkt", "//src/google/protobuf/compiler:code_generator", @@ -25,6 +27,45 @@ cc_library( ], ) +cc_library( + name = "context", + srcs = ["context.cc"], + hdrs = ["context.h"], + copts = COPTS, + include_prefix = "google/protobuf/compiler/rust", + visibility = [ + "//pkg:__pkg__", + "//src/google/protobuf/compiler:__pkg__", + ], + deps = [ + "//src/google/protobuf/compiler:code_generator", + "//src/google/protobuf/io:printer", + "@com_google_absl//absl/algorithm:container", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) + +cc_library( + name = "naming", + srcs = ["naming.cc"], + hdrs = ["naming.h"], + copts = COPTS, + include_prefix = "google/protobuf/compiler/rust", + visibility = [ + "//pkg:__pkg__", + "//src/google/protobuf/compiler:__pkg__", + ], + deps = [ + ":context", + "//src/google/protobuf:protobuf_nowkt", + "@com_google_absl//absl/log:absl_log", + "@com_google_absl//absl/strings", + ], +) + cc_library( name = "upb_kernel", srcs = ["upb_kernel.cc"], @@ -33,7 +74,7 @@ cc_library( include_prefix = "google/protobuf/compiler/rust", visibility = ["//visibility:private"], deps = [ - "@com_google_absl//absl/strings", "//src/google/protobuf:protobuf_nowkt", + "@com_google_absl//absl/strings", ], -) \ No newline at end of file +) diff --git a/src/google/protobuf/compiler/rust/context.cc b/src/google/protobuf/compiler/rust/context.cc new file mode 100644 index 0000000000..5dfadb6982 --- /dev/null +++ b/src/google/protobuf/compiler/rust/context.cc @@ -0,0 +1,96 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "google/protobuf/compiler/rust/context.h" + +#include +#include +#include + +#include "absl/algorithm/container.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "google/protobuf/compiler/code_generator.h" + +namespace google { +namespace protobuf { +namespace compiler { +namespace rust { +static constexpr std::pair kMagicValue = { + "experimental-codegen", + "enabled", +}; + +absl::StatusOr Options::Parse(absl::string_view param) { + std::vector> args; + ParseGeneratorParameter(param, &args); + + bool has_experimental_value = absl::c_any_of( + args, [](std::pair pair) { + return pair == kMagicValue; + }); + + if (!has_experimental_value) { + return absl::InvalidArgumentError( + "The Rust codegen is highly experimental. Future versions will break " + "existing code. Use at your own risk. You can opt-in by passing " + "'experimental-codegen=enabled' to '--rust_out'."); + } + + Options opts; + + auto kernel_arg = + absl::c_find_if(args, [](auto& arg) { return arg.first == "kernel"; }); + if (kernel_arg == args.end()) { + return absl::InvalidArgumentError( + "Mandatory option `kernel` missing, please specify `cpp` or " + "`upb`."); + } + + if (kernel_arg->second == "upb") { + opts.kernel = Kernel::kUpb; + } else if (kernel_arg->second == "cpp") { + opts.kernel = Kernel::kCpp; + } else { + return absl::InvalidArgumentError( + absl::Substitute("Unknown kernel `$0`, please specify `cpp` or " + "`upb`.", + kernel_arg->second)); + } + + return opts; +} + +} // namespace rust +} // namespace compiler +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/compiler/rust/context.h b/src/google/protobuf/compiler/rust/context.h new file mode 100644 index 0000000000..f3633816c4 --- /dev/null +++ b/src/google/protobuf/compiler/rust/context.h @@ -0,0 +1,112 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_COMPILER_RUST_CONTEXT_H__ +#define GOOGLE_PROTOBUF_COMPILER_RUST_CONTEXT_H__ + +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" +#include "google/protobuf/io/printer.h" + +namespace google { +namespace protobuf { +namespace compiler { +namespace rust { +// Marks which kernel the Rust codegen should generate code for. +enum class Kernel { + kUpb, + kCpp, +}; + +// Global options for a codegen invocation. +struct Options { + Kernel kernel; + + static absl::StatusOr Parse(absl::string_view param); +}; + +// A context for generating a particular kind of definition. +// This type acts as an options struct (as in go/totw/173) for most of the +// generator. +// +// `Descriptor` is the type of a descriptor.h class relevant for the current +// context. +template +class Context { + public: + Context(const Options* opts, const Descriptor* desc, io::Printer* printer) + : opts_(opts), desc_(desc), printer_(printer) {} + + Context(const Context&) = default; + Context& operator=(const Context&) = default; + + const Descriptor& desc() const { return *desc_; } + const Options& opts() const { return *opts_; } + + bool is_cpp() const { return opts_->kernel == Kernel::kCpp; } + bool is_upb() const { return opts_->kernel == Kernel::kUpb; } + + // NOTE: prefer ctx.Emit() over ctx.printer().Emit(); + io::Printer& printer() const { return *printer_; } + + // Creates a new context over a different descriptor. + template + Context WithDesc(const D& desc) const { + return Context(opts_, &desc, printer_); + } + + template + Context WithDesc(const D* desc) const { + return Context(opts_, desc, printer_); + } + + Context WithPrinter(io::Printer* printer) const { + return Context(opts_, desc_, printer); + } + + // Forwards to Emit(), which will likely be called all the time. + void Emit(absl::string_view format) const { printer_->Emit(format); } + void Emit(absl::Span vars, + absl::string_view format) const { + printer_->Emit(vars, format); + } + + private: + const Options* opts_; + const Descriptor* desc_; + io::Printer* printer_; +}; +} // namespace rust +} // namespace compiler +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_COMPILER_RUST_CONTEXT_H__ diff --git a/src/google/protobuf/compiler/rust/generator.cc b/src/google/protobuf/compiler/rust/generator.cc index 67e687044c..7a432794b1 100644 --- a/src/google/protobuf/compiler/rust/generator.cc +++ b/src/google/protobuf/compiler/rust/generator.cc @@ -30,21 +30,21 @@ #include "google/protobuf/compiler/rust/generator.h" -#include #include -#include -#include -#include "absl/algorithm/container.h" +#include "absl/log/absl_log.h" +#include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" -#include "absl/types/optional.h" +#include "google/protobuf/compiler/code_generator.h" #include "google/protobuf/compiler/cpp/helpers.h" #include "google/protobuf/compiler/cpp/names.h" +#include "google/protobuf/compiler/rust/context.h" +#include "google/protobuf/compiler/rust/naming.h" #include "google/protobuf/compiler/rust/upb_kernel.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/descriptor.pb.h" @@ -54,186 +54,72 @@ namespace google { namespace protobuf { namespace compiler { namespace rust { - -bool ExperimentalRustGeneratorEnabled( - const std::vector>& options) { - static constexpr std::pair kMagicValue = - {"experimental-codegen", "enabled"}; - - return absl::c_any_of( - options, [](std::pair pair) { - return pair == kMagicValue; - }); -} - -// Marks which kernel the Rust codegen should generate code for. -enum class Kernel { - kUpb, - kCpp, -}; - -absl::optional ParseKernelConfiguration( - const std::vector>& options) { - for (const auto& pair : options) { - if (pair.first == "kernel") { - if (pair.second == "upb") { - return Kernel::kUpb; - } - if (pair.second == "cpp") { - return Kernel::kCpp; - } - } - } - return absl::nullopt; -} - -std::string GetCrateName(const FileDescriptor* dependency) { - absl::string_view path = dependency->name(); - auto basename = path.substr(path.rfind('/') + 1); - return absl::StrReplaceAll(basename, { - {".", "_"}, - {"-", "_"}, - }); -} - -std::string GetFileExtensionForKernel(Kernel kernel) { - switch (kernel) { - case Kernel::kUpb: - return ".u.pb.rs"; - case Kernel::kCpp: - return ".c.pb.rs"; - } - ABSL_LOG(FATAL) << "Unknown kernel type: " << static_cast(kernel); - return ""; -} - -std::string RustModule(absl::string_view package) { - if (package.empty()) return ""; - return absl::StrCat("", absl::StrReplaceAll(package, {{".", "::"}})); -} - -std::string GetCrateRelativeQualifiedPath(const Descriptor* msg) { - std::string mod = RustModule(msg->file()->package()); - return absl::StrJoin({mod, msg->name()}, "::"); -} - -void EmitOpeningOfPackageModules(absl::string_view package, - google::protobuf::io::Printer& p) { - if (package.empty()) return; - for (absl::string_view segment : absl::StrSplit(package, '.')) { - p.Emit({{"segment", segment}}, - R"rs( +void EmitOpeningOfPackageModules(Context file) { + if (file.desc().package().empty()) return; + for (absl::string_view segment : absl::StrSplit(file.desc().package(), '.')) { + file.Emit({{"segment", segment}}, + R"rs( pub mod $segment$ { )rs"); } } -void EmitClosingOfPackageModules(absl::string_view package, - google::protobuf::io::Printer& p) { - if (package.empty()) return; - std::vector segments = absl::StrSplit(package, '.'); +void EmitClosingOfPackageModules(Context file) { + if (file.desc().package().empty()) return; + std::vector segments = + absl::StrSplit(file.desc().package(), '.'); absl::c_reverse(segments); for (absl::string_view segment : segments) { - p.Emit({{"segment", segment}}, - R"rs( + file.Emit({{"segment", segment}}, + R"rs( } // mod $segment$ )rs"); } } -std::string GetUnderscoreDelimitedFullName(const Descriptor* msg) { - std::string result = msg->full_name(); - absl::StrReplaceAll({{".", "_"}}, &result); - return result; -} - -std::string GetAccessorThunkName( - const FieldDescriptor* field, absl::string_view op, - absl::string_view underscore_delimited_full_name) { - return absl::Substitute("__rust_proto_thunk__$0_$1_$2", - underscore_delimited_full_name, op, field->name()); -} - -bool IsSupportedFieldType(const FieldDescriptor* field) { - return !field->is_repeated() && - // We do not support [ctype=FOO] (used to set the field type in C++ to - // cord or string_piece) in V0 API. - !field->options().has_ctype() && - (field->type() == FieldDescriptor::TYPE_BOOL || - field->type() == FieldDescriptor::TYPE_INT64 || - field->type() == FieldDescriptor::TYPE_BYTES); -} - -absl::string_view PrimitiveRsTypeName(const FieldDescriptor* field) { - switch (field->type()) { - case FieldDescriptor::TYPE_BOOL: - return "bool"; - case FieldDescriptor::TYPE_INT64: - return "i64"; - case FieldDescriptor::TYPE_BYTES: - return "&[u8]"; - default: - break; - } - ABSL_LOG(FATAL) << "Unsupported field type: " << field->type_name(); - return ""; -} - -void EmitGetterExpr(const FieldDescriptor* field, google::protobuf::io::Printer& p, - absl::string_view underscore_delimited_full_name) { - std::string thunk_name = - GetAccessorThunkName(field, "get", underscore_delimited_full_name); - switch (field->type()) { +void EmitGetterExpr(Context field) { + std::string thunk_name = GetAccessorThunkName(field, "get"); + switch (field.desc().type()) { case FieldDescriptor::TYPE_BYTES: - p.Emit({{"getter_thunk_name", thunk_name}}, - R"rs( + field.Emit({{"getter_thunk_name", thunk_name}}, + R"rs( let val = unsafe { $getter_thunk_name$(self.msg) }; Some(unsafe { ::__std::slice::from_raw_parts(val.ptr, val.len) }) )rs"); return; default: - p.Emit({{"getter_thunk_name", thunk_name}}, - R"rs( + field.Emit({{"getter_thunk_name", thunk_name}}, + R"rs( Some(unsafe { $getter_thunk_name$(self.msg) }) )rs"); } } -void GenerateAccessorFns(const Descriptor* msg, google::protobuf::io::Printer& p, - absl::string_view underscore_delimited_full_name) { - for (int i = 0; i < msg->field_count(); ++i) { - const FieldDescriptor* field = msg->field(i); +void GenerateAccessorFns(Context msg) { + for (int i = 0; i < msg.desc().field_count(); ++i) { + auto field = msg.WithDesc(msg.desc().field(i)); if (!IsSupportedFieldType(field)) { continue; } - p.Emit( + field.Emit( { - {"field_name", field->name()}, + {"field_name", field.desc().name()}, {"FieldType", PrimitiveRsTypeName(field)}, - {"hazzer_thunk_name", - GetAccessorThunkName(field, "has", - underscore_delimited_full_name)}, - {"getter_thunk_name", - GetAccessorThunkName(field, "get", - underscore_delimited_full_name)}, - {"getter_expr", - [&] { EmitGetterExpr(field, p, underscore_delimited_full_name); }}, - {"setter_thunk_name", - GetAccessorThunkName(field, "set", - underscore_delimited_full_name)}, + {"hazzer_thunk_name", GetAccessorThunkName(field, "has")}, + {"getter_thunk_name", GetAccessorThunkName(field, "get")}, + {"getter_expr", [&] { EmitGetterExpr(field); }}, + {"setter_thunk_name", GetAccessorThunkName(field, "set")}, {"setter_args", [&] { - switch (field->type()) { + switch (field.desc().type()) { case FieldDescriptor::TYPE_BYTES: - p.Emit("val.as_ptr(), val.len()"); + field.Emit("val.as_ptr(), val.len()"); return; default: - p.Emit("val"); + field.Emit("val"); } }}, - {"clearer_thunk_name", - GetAccessorThunkName(field, "clear", - underscore_delimited_full_name)}, + {"clearer_thunk_name", GetAccessorThunkName(field, "clear")}, }, R"rs( pub fn $field_name$(&self) -> Option<$FieldType$> { @@ -252,50 +138,40 @@ void GenerateAccessorFns(const Descriptor* msg, google::protobuf::io::Printer& p } } -void GenerateAccessorThunkRsDeclarations( - const Descriptor* msg, google::protobuf::io::Printer& p, - std::string underscore_delimited_full_name) { - for (int i = 0; i < msg->field_count(); ++i) { - const FieldDescriptor* field = msg->field(i); +void GenerateAccessorThunkRsDeclarations(Context msg) { + for (int i = 0; i < msg.desc().field_count(); ++i) { + auto field = msg.WithDesc(msg.desc().field(i)); if (!IsSupportedFieldType(field)) { continue; } absl::string_view type_name = PrimitiveRsTypeName(field); - p.Emit( + field.Emit( { {"FieldType", type_name}, {"GetterReturnType", [&] { - switch (field->type()) { + switch (field.desc().type()) { case FieldDescriptor::TYPE_BYTES: - p.Emit("::__pb::PtrAndLen"); + field.Emit("::__pb::PtrAndLen"); return; default: - p.Emit(type_name); + field.Emit(type_name); } }}, - {"hazzer_thunk_name", - GetAccessorThunkName(field, "has", - underscore_delimited_full_name)}, - {"getter_thunk_name", - GetAccessorThunkName(field, "get", - underscore_delimited_full_name)}, - {"setter_thunk_name", - GetAccessorThunkName(field, "set", - underscore_delimited_full_name)}, + {"hazzer_thunk_name", GetAccessorThunkName(field, "has")}, + {"getter_thunk_name", GetAccessorThunkName(field, "get")}, + {"setter_thunk_name", GetAccessorThunkName(field, "set")}, {"setter_params", [&] { - switch (field->type()) { + switch (field.desc().type()) { case FieldDescriptor::TYPE_BYTES: - p.Emit("val: *const u8, len: usize"); + field.Emit("val: *const u8, len: usize"); return; default: - p.Emit({{"type_name", type_name}}, "val: $type_name$"); + field.Emit({{"type_name", type_name}}, "val: $type_name$"); } }}, - {"clearer_thunk_name", - GetAccessorThunkName(field, "clear", - underscore_delimited_full_name)}, + {"clearer_thunk_name", GetAccessorThunkName(field, "clear")}, }, R"rs( fn $hazzer_thunk_name$(raw_msg: ::__std::ptr::NonNull) -> bool; @@ -306,71 +182,65 @@ void GenerateAccessorThunkRsDeclarations( } } -void GenerateAccessorThunksCcDefinitions( - const Descriptor* msg, google::protobuf::io::Printer& p, - absl::string_view underscore_delimited_full_name) { - for (int i = 0; i < msg->field_count(); ++i) { - const FieldDescriptor* field = msg->field(i); +void GenerateAccessorThunksCcDefinitions(Context msg) { + for (int i = 0; i < msg.desc().field_count(); ++i) { + auto field = msg.WithDesc(msg.desc().field(i)); if (!IsSupportedFieldType(field)) { continue; } - const char* type_name = cpp::PrimitiveTypeName(field->cpp_type()); - p.Emit( - {{"field_name", field->name()}, + absl::string_view type_name = + cpp::PrimitiveTypeName(field.desc().cpp_type()); + field.Emit( + {{"field_name", field.desc().name()}, {"FieldType", type_name}, {"GetterReturnType", [&] { - switch (field->type()) { + switch (field.desc().type()) { case FieldDescriptor::TYPE_BYTES: - p.Emit("::google::protobuf::rust_internal::PtrAndLen"); + field.Emit("::google::protobuf::rust_internal::PtrAndLen"); return; default: - p.Emit(type_name); + field.Emit(type_name); } }}, - {"namespace", cpp::Namespace(msg)}, - {"hazzer_thunk_name", - GetAccessorThunkName(field, "has", underscore_delimited_full_name)}, - {"getter_thunk_name", - GetAccessorThunkName(field, "get", underscore_delimited_full_name)}, + {"namespace", cpp::Namespace(&field.desc())}, + {"hazzer_thunk_name", GetAccessorThunkName(field, "has")}, + {"getter_thunk_name", GetAccessorThunkName(field, "get")}, {"getter_body", [&] { - switch (field->type()) { + switch (field.desc().type()) { case FieldDescriptor::TYPE_BYTES: - p.Emit({{"field_name", field->name()}}, R"cc( + field.Emit({{"field_name", field.desc().name()}}, R"cc( absl::string_view val = msg->$field_name$(); return google::protobuf::rust_internal::PtrAndLen(val.data(), val.size()); )cc"); return; default: - p.Emit(R"cc(return msg->$field_name$();)cc"); + field.Emit(R"cc(return msg->$field_name$();)cc"); } }}, - {"setter_thunk_name", - GetAccessorThunkName(field, "set", underscore_delimited_full_name)}, + {"setter_thunk_name", GetAccessorThunkName(field, "set")}, {"setter_params", [&] { - switch (field->type()) { + switch (field.desc().type()) { case FieldDescriptor::TYPE_BYTES: - p.Emit("const char* ptr, size_t size"); + field.Emit("const char* ptr, size_t size"); return; default: - p.Emit({{"type_name", type_name}}, "$type_name$ val"); + field.Emit({{"type_name", type_name}}, "$type_name$ val"); } }}, {"setter_args", [&] { - switch (field->type()) { + switch (field.desc().type()) { case FieldDescriptor::TYPE_BYTES: - p.Emit("absl::string_view(ptr, size)"); + field.Emit("absl::string_view(ptr, size)"); return; default: - p.Emit("val"); + field.Emit("val"); } }}, - {"clearer_thunk_name", - GetAccessorThunkName(field, "clear", - underscore_delimited_full_name)}}, + {"clearer_thunk_name", GetAccessorThunkName(field, "clear")}}, R"cc( extern "C" { bool $hazzer_thunk_name$($namespace$::$Msg$* msg) { @@ -390,25 +260,16 @@ void GenerateAccessorThunksCcDefinitions( } } -void GenerateForCpp(const FileDescriptor* file, google::protobuf::io::Printer& p) { - for (int i = 0; i < file->message_type_count(); ++i) { - const Descriptor* msg = file->message_type(i); - std::string underscore_delimited_full_name = - GetUnderscoreDelimitedFullName(msg); - p.Emit( +void GenerateForCpp(Context file) { + for (int i = 0; i < file.desc().message_type_count(); ++i) { + auto msg = file.WithDesc(file.desc().message_type(i)); + msg.Emit( { - {"Msg", msg->name()}, - {"pkg_Msg", underscore_delimited_full_name}, - {"accessor_fns", - [&] { - GenerateAccessorFns(file->message_type(i), p, - underscore_delimited_full_name); - }}, + {"Msg", msg.desc().name()}, + {"pkg_Msg", GetUnderscoreDelimitedFullName(msg)}, + {"accessor_fns", [&] { GenerateAccessorFns(msg); }}, {"accessor_thunks", - [&] { - GenerateAccessorThunkRsDeclarations( - file->message_type(i), p, underscore_delimited_full_name); - }}, + [&] { GenerateAccessorThunkRsDeclarations(msg); }}, }, R"rs( #[allow(non_camel_case_types)] @@ -451,21 +312,16 @@ void GenerateForCpp(const FileDescriptor* file, google::protobuf::io::Printer& p } } -void GenerateThunksForCpp(const FileDescriptor* file, google::protobuf::io::Printer& p) { - for (int i = 0; i < file->message_type_count(); ++i) { - const Descriptor* msg = file->message_type(i); - std::string underscore_delimited_full_name = - GetUnderscoreDelimitedFullName(msg); - p.Emit( +void GenerateThunksForCpp(Context file) { + for (int i = 0; i < file.desc().message_type_count(); ++i) { + auto msg = file.WithDesc(file.desc().message_type(i)); + file.Emit( { - {"Msg", msg->name()}, + {"Msg", msg.desc().name()}, {"pkg_Msg", GetUnderscoreDelimitedFullName(msg)}, - {"namespace", cpp::Namespace(msg)}, + {"namespace", cpp::Namespace(&msg.desc())}, {"accessor_thunks", - [&] { - GenerateAccessorThunksCcDefinitions( - file->message_type(i), p, underscore_delimited_full_name); - }}, + [&] { GenerateAccessorThunksCcDefinitions(msg); }}, }, R"cc( extern "C" { @@ -499,78 +355,67 @@ std::string GetKernelRustName(Kernel kernel) { return ""; } -bool RustGenerator::Generate(const FileDescriptor* file, +bool RustGenerator::Generate(const FileDescriptor* file_desc, const std::string& parameter, GeneratorContext* generator_context, std::string* error) const { - std::vector> options; - ParseGeneratorParameter(parameter, &options); - - if (!ExperimentalRustGeneratorEnabled(options)) { - *error = - "The Rust codegen is highly experimental. Future versions will break " - "existing code. Use at your own risk. You can opt-in by passing " - "'experimental-codegen=enabled' to '--rust_out'."; + absl::StatusOr opts = Options::Parse(parameter); + if (!opts.ok()) { + *error = std::string(opts.status().message()); return false; } - absl::optional kernel = ParseKernelConfiguration(options); - if (!kernel.has_value()) { - *error = - "Mandatory option `kernel` missing, please specify `cpp` or " - "`upb`."; - return false; - } + Context file(&*opts, file_desc, nullptr); - auto basename = StripProto(file->name()); - auto outfile = absl::WrapUnique(generator_context->Open( - absl::StrCat(basename, GetFileExtensionForKernel(*kernel)))); + auto outfile = absl::WrapUnique(generator_context->Open(GetRsFile(file))); + io::Printer printer(outfile.get()); + file = file.WithPrinter(&printer); - google::protobuf::io::Printer p(outfile.get()); - p.Emit({{"kernel", GetKernelRustName(*kernel)}}, R"rs( + file.Emit({{"kernel", GetKernelRustName(opts->kernel)}}, R"rs( extern crate protobuf_$kernel$ as __pb; extern crate std as __std; )rs"); - EmitOpeningOfPackageModules(file->package(), p); + EmitOpeningOfPackageModules(file); // TODO(b/270124215): Delete the following "placeholder impl" of `import // public`. Also make sure to figure out how to map FileDescriptor#name to // Rust crate names (currently Bazel labels). - for (int i = 0; i < file->public_dependency_count(); ++i) { - const FileDescriptor* dep = file->public_dependency(i); + for (int i = 0; i < file.desc().public_dependency_count(); ++i) { + auto dep = file.WithDesc(file.desc().public_dependency(i)); std::string crate_name = GetCrateName(dep); - for (int j = 0; j < dep->message_type_count(); ++j) { + for (int j = 0; j < dep.desc().message_type_count(); ++j) { + auto msg = file.WithDesc(dep.desc().message_type(j)); // TODO(b/272728844): Implement real logic - p.Emit( - {{"crate", crate_name}, - {"pkg::Msg", GetCrateRelativeQualifiedPath(dep->message_type(j))}}, - R"rs( + file.Emit({{"crate", crate_name}, + {"pkg::Msg", GetCrateRelativeQualifiedPath(msg)}}, + R"rs( pub use $crate$::$pkg::Msg$; )rs"); } } - switch (*kernel) { + switch (opts->kernel) { case Kernel::kUpb: - rust::UpbKernel k; - k.Generate(file, p); + rust::UpbKernel().Generate(&file.desc(), printer); break; case Kernel::kCpp: - GenerateForCpp(file, p); + GenerateForCpp(file); - auto thunksfile = absl::WrapUnique( - generator_context->Open(absl::StrCat(basename, ".pb.thunks.cc"))); + auto thunksfile = + absl::WrapUnique(generator_context->Open(GetThunkCcFile(file))); google::protobuf::io::Printer thunks(thunksfile.get()); - thunks.Emit({{"basename", basename}}, - R"cc( -#include "$basename$.pb.h" + auto thunk_file = file.WithPrinter(&thunks); + + thunk_file.Emit({{"proto_h", GetHeaderFile(file)}}, + R"cc( +#include "$proto_h$" #include "google/protobuf/rust/cpp_kernel/cpp_api.h" - )cc"); - GenerateThunksForCpp(file, thunks); + )cc"); + GenerateThunksForCpp(thunk_file); break; } - EmitClosingOfPackageModules(file->package(), p); + EmitClosingOfPackageModules(file); return true; } diff --git a/src/google/protobuf/compiler/rust/generator.h b/src/google/protobuf/compiler/rust/generator.h index c023685a29..c10a712b4b 100644 --- a/src/google/protobuf/compiler/rust/generator.h +++ b/src/google/protobuf/compiler/rust/generator.h @@ -31,6 +31,7 @@ #ifndef GOOGLE_PROTOBUF_COMPILER_RUST_GENERATOR_H__ #define GOOGLE_PROTOBUF_COMPILER_RUST_GENERATOR_H__ +#include #include #include "google/protobuf/compiler/code_generator.h" diff --git a/src/google/protobuf/compiler/rust/naming.cc b/src/google/protobuf/compiler/rust/naming.cc new file mode 100644 index 0000000000..7dc02ab8e6 --- /dev/null +++ b/src/google/protobuf/compiler/rust/naming.cc @@ -0,0 +1,130 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "google/protobuf/compiler/rust/naming.h" + +#include + +#include "absl/log/absl_log.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "google/protobuf/compiler/code_generator.h" +#include "google/protobuf/compiler/rust/context.h" +#include "google/protobuf/descriptor.h" + +namespace google { +namespace protobuf { +namespace compiler { +namespace rust { +std::string GetCrateName(Context dep) { + absl::string_view path = dep.desc().name(); + auto basename = path.substr(path.rfind('/') + 1); + return absl::StrReplaceAll(basename, {{".", "_"}, {"-", "_"}}); +} + +std::string GetRsFile(Context file) { + auto basename = StripProto(file.desc().name()); + switch (auto k = file.opts().kernel) { + case Kernel::kUpb: + return absl::StrCat(basename, ".u.pb.rs"); + case Kernel::kCpp: + return absl::StrCat(basename, ".c.pb.rs"); + default: + ABSL_LOG(FATAL) << "Unknown kernel type: " << static_cast(k); + return ""; + } +} + +std::string GetThunkCcFile(Context file) { + auto basename = StripProto(file.desc().name()); + return absl::StrCat(basename, ".pb.thunks.cc"); +} + +std::string GetHeaderFile(Context file) { + auto basename = StripProto(file.desc().name()); + return absl::StrCat(basename, ".proto.h"); +} + +std::string GetUnderscoreDelimitedFullName(Context msg) { + std::string result = msg.desc().full_name(); + absl::StrReplaceAll({{".", "_"}}, &result); + return result; +} + +std::string GetAccessorThunkName(Context field, + absl::string_view op) { + std::string thunk = "__rust_proto_thunk__"; + absl::StrAppend(&thunk, GetUnderscoreDelimitedFullName( + field.WithDesc(field.desc().containing_type()))); + absl::SubstituteAndAppend(&thunk, "_$0_$1", op, field.desc().name()); + return thunk; +} + +absl::string_view PrimitiveRsTypeName(Context field) { + switch (field.desc().type()) { + case FieldDescriptor::TYPE_BOOL: + return "bool"; + case FieldDescriptor::TYPE_INT64: + return "i64"; + case FieldDescriptor::TYPE_BYTES: + return "&[u8]"; + default: + break; + } + ABSL_LOG(FATAL) << "Unsupported field type: " << field.desc().type_name(); + return ""; +} + +bool IsSupportedFieldType(Context field) { + return !field.desc().is_repeated() && + // We do not support [ctype=FOO] (used to set the field type in C++ to + // cord or string_piece) in V0 API. + !field.desc().options().has_ctype() && + (field.desc().type() == FieldDescriptor::TYPE_BOOL || + field.desc().type() == FieldDescriptor::TYPE_INT64 || + field.desc().type() == FieldDescriptor::TYPE_BYTES); +} + +std::string RustModule(Context msg) { + absl::string_view package = msg.desc().file()->package(); + if (package.empty()) return ""; + return absl::StrCat("", absl::StrReplaceAll(package, {{".", "::"}})); +} + +std::string GetCrateRelativeQualifiedPath(Context msg) { + return absl::StrCat(RustModule(msg), "::", msg.desc().name()); +} +} // namespace rust +} // namespace compiler +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/compiler/rust/naming.h b/src/google/protobuf/compiler/rust/naming.h new file mode 100644 index 0000000000..dabc0527ed --- /dev/null +++ b/src/google/protobuf/compiler/rust/naming.h @@ -0,0 +1,68 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef GOOGLE_PROTOBUF_COMPILER_RUST_NAMING_H__ +#define GOOGLE_PROTOBUF_COMPILER_RUST_NAMING_H__ + +#include + +#include "absl/strings/string_view.h" +#include "google/protobuf/compiler/rust/context.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/descriptor.pb.h" + +namespace google { +namespace protobuf { +namespace compiler { +namespace rust { +std::string GetCrateName(Context dep); + +std::string GetRsFile(Context file); +std::string GetThunkCcFile(Context file); +std::string GetHeaderFile(Context file); + +std::string GetUnderscoreDelimitedFullName(Context msg); + +std::string GetAccessorThunkName(Context field, + absl::string_view op); + +bool IsSupportedFieldType(Context field); + +absl::string_view PrimitiveRsTypeName(Context field); + +std::string RustModule(Context msg); + +std::string GetCrateRelativeQualifiedPath(Context msg); +} // namespace rust +} // namespace compiler +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_COMPILER_RUST_NAMING_H__