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
pull/12463/head
Protobuf Team Bot 2 years ago committed by Copybara-Service
parent a05c57d43c
commit 7143844db0
  1. 45
      src/google/protobuf/compiler/rust/BUILD.bazel
  2. 96
      src/google/protobuf/compiler/rust/context.cc
  3. 112
      src/google/protobuf/compiler/rust/context.h
  4. 393
      src/google/protobuf/compiler/rust/generator.cc
  5. 1
      src/google/protobuf/compiler/rust/generator.h
  6. 130
      src/google/protobuf/compiler/rust/naming.cc
  7. 68
      src/google/protobuf/compiler/rust/naming.h

@ -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",
],
)
)

@ -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 <string>
#include <utility>
#include <vector>
#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<absl::string_view, absl::string_view> kMagicValue = {
"experimental-codegen",
"enabled",
};
absl::StatusOr<Options> Options::Parse(absl::string_view param) {
std::vector<std::pair<std::string, std::string>> args;
ParseGeneratorParameter(param, &args);
bool has_experimental_value = absl::c_any_of(
args, [](std::pair<absl::string_view, absl::string_view> 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

@ -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<Options> 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 <typename Descriptor>
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 <typename D>
Context<D> WithDesc(const D& desc) const {
return Context<D>(opts_, &desc, printer_);
}
template <typename D>
Context<D> WithDesc(const D* desc) const {
return Context<D>(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<const io::Printer::Sub> 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__

@ -30,21 +30,21 @@
#include "google/protobuf/compiler/rust/generator.h"
#include <optional>
#include <string>
#include <utility>
#include <vector>
#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<std::pair<std::string, std::string>>& options) {
static constexpr std::pair<absl::string_view, absl::string_view> kMagicValue =
{"experimental-codegen", "enabled"};
return absl::c_any_of(
options, [](std::pair<absl::string_view, absl::string_view> pair) {
return pair == kMagicValue;
});
}
// Marks which kernel the Rust codegen should generate code for.
enum class Kernel {
kUpb,
kCpp,
};
absl::optional<Kernel> ParseKernelConfiguration(
const std::vector<std::pair<std::string, std::string>>& 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<int>(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<FileDescriptor> 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<absl::string_view> segments = absl::StrSplit(package, '.');
void EmitClosingOfPackageModules(Context<FileDescriptor> file) {
if (file.desc().package().empty()) return;
std::vector<absl::string_view> 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<FieldDescriptor> 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<Descriptor> 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<Descriptor> 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<u8>) -> 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<Descriptor> 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<FileDescriptor> 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<FileDescriptor> 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<std::pair<std::string, std::string>> 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<Options> opts = Options::Parse(parameter);
if (!opts.ok()) {
*error = std::string(opts.status().message());
return false;
}
absl::optional<Kernel> kernel = ParseKernelConfiguration(options);
if (!kernel.has_value()) {
*error =
"Mandatory option `kernel` missing, please specify `cpp` or "
"`upb`.";
return false;
}
Context<FileDescriptor> 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;
}

@ -31,6 +31,7 @@
#ifndef GOOGLE_PROTOBUF_COMPILER_RUST_GENERATOR_H__
#define GOOGLE_PROTOBUF_COMPILER_RUST_GENERATOR_H__
#include <cstdint>
#include <string>
#include "google/protobuf/compiler/code_generator.h"

@ -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 <string>
#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<FileDescriptor> dep) {
absl::string_view path = dep.desc().name();
auto basename = path.substr(path.rfind('/') + 1);
return absl::StrReplaceAll(basename, {{".", "_"}, {"-", "_"}});
}
std::string GetRsFile(Context<FileDescriptor> 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<int>(k);
return "";
}
}
std::string GetThunkCcFile(Context<FileDescriptor> file) {
auto basename = StripProto(file.desc().name());
return absl::StrCat(basename, ".pb.thunks.cc");
}
std::string GetHeaderFile(Context<FileDescriptor> file) {
auto basename = StripProto(file.desc().name());
return absl::StrCat(basename, ".proto.h");
}
std::string GetUnderscoreDelimitedFullName(Context<Descriptor> msg) {
std::string result = msg.desc().full_name();
absl::StrReplaceAll({{".", "_"}}, &result);
return result;
}
std::string GetAccessorThunkName(Context<FieldDescriptor> 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<FieldDescriptor> 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<FieldDescriptor> 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<Descriptor> msg) {
absl::string_view package = msg.desc().file()->package();
if (package.empty()) return "";
return absl::StrCat("", absl::StrReplaceAll(package, {{".", "::"}}));
}
std::string GetCrateRelativeQualifiedPath(Context<Descriptor> msg) {
return absl::StrCat(RustModule(msg), "::", msg.desc().name());
}
} // namespace rust
} // namespace compiler
} // namespace protobuf
} // namespace google

@ -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 <string>
#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<FileDescriptor> dep);
std::string GetRsFile(Context<FileDescriptor> file);
std::string GetThunkCcFile(Context<FileDescriptor> file);
std::string GetHeaderFile(Context<FileDescriptor> file);
std::string GetUnderscoreDelimitedFullName(Context<Descriptor> msg);
std::string GetAccessorThunkName(Context<FieldDescriptor> field,
absl::string_view op);
bool IsSupportedFieldType(Context<FieldDescriptor> field);
absl::string_view PrimitiveRsTypeName(Context<FieldDescriptor> field);
std::string RustModule(Context<Descriptor> msg);
std::string GetCrateRelativeQualifiedPath(Context<Descriptor> msg);
} // namespace rust
} // namespace compiler
} // namespace protobuf
} // namespace google
#endif // GOOGLE_PROTOBUF_COMPILER_RUST_NAMING_H__
Loading…
Cancel
Save