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. 43
      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. 379
      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__", "//src/google/protobuf/compiler:__pkg__",
], ],
deps = [ deps = [
":context",
":naming",
":upb_kernel", ":upb_kernel",
"//src/google/protobuf:protobuf_nowkt", "//src/google/protobuf:protobuf_nowkt",
"//src/google/protobuf/compiler:code_generator", "//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( cc_library(
name = "upb_kernel", name = "upb_kernel",
srcs = ["upb_kernel.cc"], srcs = ["upb_kernel.cc"],
@ -33,7 +74,7 @@ cc_library(
include_prefix = "google/protobuf/compiler/rust", include_prefix = "google/protobuf/compiler/rust",
visibility = ["//visibility:private"], visibility = ["//visibility:private"],
deps = [ deps = [
"@com_google_absl//absl/strings",
"//src/google/protobuf:protobuf_nowkt", "//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 "google/protobuf/compiler/rust/generator.h"
#include <optional>
#include <string> #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_cat.h"
#include "absl/strings/str_join.h" #include "absl/strings/str_join.h"
#include "absl/strings/str_replace.h" #include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h" #include "absl/strings/str_split.h"
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#include "absl/strings/substitute.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/helpers.h"
#include "google/protobuf/compiler/cpp/names.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/compiler/rust/upb_kernel.h"
#include "google/protobuf/descriptor.h" #include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h" #include "google/protobuf/descriptor.pb.h"
@ -54,186 +54,72 @@ namespace google {
namespace protobuf { namespace protobuf {
namespace compiler { namespace compiler {
namespace rust { namespace rust {
void EmitOpeningOfPackageModules(Context<FileDescriptor> file) {
bool ExperimentalRustGeneratorEnabled( if (file.desc().package().empty()) return;
const std::vector<std::pair<std::string, std::string>>& options) { for (absl::string_view segment : absl::StrSplit(file.desc().package(), '.')) {
static constexpr std::pair<absl::string_view, absl::string_view> kMagicValue = file.Emit({{"segment", segment}},
{"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( R"rs(
pub mod $segment$ { pub mod $segment$ {
)rs"); )rs");
} }
} }
void EmitClosingOfPackageModules(absl::string_view package, void EmitClosingOfPackageModules(Context<FileDescriptor> file) {
google::protobuf::io::Printer& p) { if (file.desc().package().empty()) return;
if (package.empty()) return; std::vector<absl::string_view> segments =
std::vector<absl::string_view> segments = absl::StrSplit(package, '.'); absl::StrSplit(file.desc().package(), '.');
absl::c_reverse(segments); absl::c_reverse(segments);
for (absl::string_view segment : segments) { for (absl::string_view segment : segments) {
p.Emit({{"segment", segment}}, file.Emit({{"segment", segment}},
R"rs( R"rs(
} // mod $segment$ } // mod $segment$
)rs"); )rs");
} }
} }
std::string GetUnderscoreDelimitedFullName(const Descriptor* msg) { void EmitGetterExpr(Context<FieldDescriptor> field) {
std::string result = msg->full_name(); std::string thunk_name = GetAccessorThunkName(field, "get");
absl::StrReplaceAll({{".", "_"}}, &result); switch (field.desc().type()) {
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: case FieldDescriptor::TYPE_BYTES:
return "&[u8]"; field.Emit({{"getter_thunk_name", thunk_name}},
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()) {
case FieldDescriptor::TYPE_BYTES:
p.Emit({{"getter_thunk_name", thunk_name}},
R"rs( R"rs(
let val = unsafe { $getter_thunk_name$(self.msg) }; let val = unsafe { $getter_thunk_name$(self.msg) };
Some(unsafe { ::__std::slice::from_raw_parts(val.ptr, val.len) }) Some(unsafe { ::__std::slice::from_raw_parts(val.ptr, val.len) })
)rs"); )rs");
return; return;
default: default:
p.Emit({{"getter_thunk_name", thunk_name}}, field.Emit({{"getter_thunk_name", thunk_name}},
R"rs( R"rs(
Some(unsafe { $getter_thunk_name$(self.msg) }) Some(unsafe { $getter_thunk_name$(self.msg) })
)rs"); )rs");
} }
} }
void GenerateAccessorFns(const Descriptor* msg, google::protobuf::io::Printer& p, void GenerateAccessorFns(Context<Descriptor> msg) {
absl::string_view underscore_delimited_full_name) { for (int i = 0; i < msg.desc().field_count(); ++i) {
for (int i = 0; i < msg->field_count(); ++i) { auto field = msg.WithDesc(msg.desc().field(i));
const FieldDescriptor* field = msg->field(i);
if (!IsSupportedFieldType(field)) { if (!IsSupportedFieldType(field)) {
continue; continue;
} }
p.Emit( field.Emit(
{ {
{"field_name", field->name()}, {"field_name", field.desc().name()},
{"FieldType", PrimitiveRsTypeName(field)}, {"FieldType", PrimitiveRsTypeName(field)},
{"hazzer_thunk_name", {"hazzer_thunk_name", GetAccessorThunkName(field, "has")},
GetAccessorThunkName(field, "has", {"getter_thunk_name", GetAccessorThunkName(field, "get")},
underscore_delimited_full_name)}, {"getter_expr", [&] { EmitGetterExpr(field); }},
{"getter_thunk_name", {"setter_thunk_name", GetAccessorThunkName(field, "set")},
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)},
{"setter_args", {"setter_args",
[&] { [&] {
switch (field->type()) { switch (field.desc().type()) {
case FieldDescriptor::TYPE_BYTES: case FieldDescriptor::TYPE_BYTES:
p.Emit("val.as_ptr(), val.len()"); field.Emit("val.as_ptr(), val.len()");
return; return;
default: default:
p.Emit("val"); field.Emit("val");
} }
}}, }},
{"clearer_thunk_name", {"clearer_thunk_name", GetAccessorThunkName(field, "clear")},
GetAccessorThunkName(field, "clear",
underscore_delimited_full_name)},
}, },
R"rs( R"rs(
pub fn $field_name$(&self) -> Option<$FieldType$> { pub fn $field_name$(&self) -> Option<$FieldType$> {
@ -252,50 +138,40 @@ void GenerateAccessorFns(const Descriptor* msg, google::protobuf::io::Printer& p
} }
} }
void GenerateAccessorThunkRsDeclarations( void GenerateAccessorThunkRsDeclarations(Context<Descriptor> msg) {
const Descriptor* msg, google::protobuf::io::Printer& p, for (int i = 0; i < msg.desc().field_count(); ++i) {
std::string underscore_delimited_full_name) { auto field = msg.WithDesc(msg.desc().field(i));
for (int i = 0; i < msg->field_count(); ++i) {
const FieldDescriptor* field = msg->field(i);
if (!IsSupportedFieldType(field)) { if (!IsSupportedFieldType(field)) {
continue; continue;
} }
absl::string_view type_name = PrimitiveRsTypeName(field); absl::string_view type_name = PrimitiveRsTypeName(field);
p.Emit( field.Emit(
{ {
{"FieldType", type_name}, {"FieldType", type_name},
{"GetterReturnType", {"GetterReturnType",
[&] { [&] {
switch (field->type()) { switch (field.desc().type()) {
case FieldDescriptor::TYPE_BYTES: case FieldDescriptor::TYPE_BYTES:
p.Emit("::__pb::PtrAndLen"); field.Emit("::__pb::PtrAndLen");
return; return;
default: default:
p.Emit(type_name); field.Emit(type_name);
} }
}}, }},
{"hazzer_thunk_name", {"hazzer_thunk_name", GetAccessorThunkName(field, "has")},
GetAccessorThunkName(field, "has", {"getter_thunk_name", GetAccessorThunkName(field, "get")},
underscore_delimited_full_name)}, {"setter_thunk_name", GetAccessorThunkName(field, "set")},
{"getter_thunk_name",
GetAccessorThunkName(field, "get",
underscore_delimited_full_name)},
{"setter_thunk_name",
GetAccessorThunkName(field, "set",
underscore_delimited_full_name)},
{"setter_params", {"setter_params",
[&] { [&] {
switch (field->type()) { switch (field.desc().type()) {
case FieldDescriptor::TYPE_BYTES: case FieldDescriptor::TYPE_BYTES:
p.Emit("val: *const u8, len: usize"); field.Emit("val: *const u8, len: usize");
return; return;
default: default:
p.Emit({{"type_name", type_name}}, "val: $type_name$"); field.Emit({{"type_name", type_name}}, "val: $type_name$");
} }
}}, }},
{"clearer_thunk_name", {"clearer_thunk_name", GetAccessorThunkName(field, "clear")},
GetAccessorThunkName(field, "clear",
underscore_delimited_full_name)},
}, },
R"rs( R"rs(
fn $hazzer_thunk_name$(raw_msg: ::__std::ptr::NonNull<u8>) -> bool; fn $hazzer_thunk_name$(raw_msg: ::__std::ptr::NonNull<u8>) -> bool;
@ -306,71 +182,65 @@ void GenerateAccessorThunkRsDeclarations(
} }
} }
void GenerateAccessorThunksCcDefinitions( void GenerateAccessorThunksCcDefinitions(Context<Descriptor> msg) {
const Descriptor* msg, google::protobuf::io::Printer& p, for (int i = 0; i < msg.desc().field_count(); ++i) {
absl::string_view underscore_delimited_full_name) { auto field = msg.WithDesc(msg.desc().field(i));
for (int i = 0; i < msg->field_count(); ++i) {
const FieldDescriptor* field = msg->field(i);
if (!IsSupportedFieldType(field)) { if (!IsSupportedFieldType(field)) {
continue; continue;
} }
const char* type_name = cpp::PrimitiveTypeName(field->cpp_type()); absl::string_view type_name =
p.Emit( cpp::PrimitiveTypeName(field.desc().cpp_type());
{{"field_name", field->name()}, field.Emit(
{{"field_name", field.desc().name()},
{"FieldType", type_name}, {"FieldType", type_name},
{"GetterReturnType", {"GetterReturnType",
[&] { [&] {
switch (field->type()) { switch (field.desc().type()) {
case FieldDescriptor::TYPE_BYTES: case FieldDescriptor::TYPE_BYTES:
p.Emit("::google::protobuf::rust_internal::PtrAndLen"); field.Emit("::google::protobuf::rust_internal::PtrAndLen");
return; return;
default: default:
p.Emit(type_name); field.Emit(type_name);
} }
}}, }},
{"namespace", cpp::Namespace(msg)}, {"namespace", cpp::Namespace(&field.desc())},
{"hazzer_thunk_name", {"hazzer_thunk_name", GetAccessorThunkName(field, "has")},
GetAccessorThunkName(field, "has", underscore_delimited_full_name)}, {"getter_thunk_name", GetAccessorThunkName(field, "get")},
{"getter_thunk_name",
GetAccessorThunkName(field, "get", underscore_delimited_full_name)},
{"getter_body", {"getter_body",
[&] { [&] {
switch (field->type()) { switch (field.desc().type()) {
case FieldDescriptor::TYPE_BYTES: 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$(); absl::string_view val = msg->$field_name$();
return google::protobuf::rust_internal::PtrAndLen(val.data(), val.size()); return google::protobuf::rust_internal::PtrAndLen(val.data(), val.size());
)cc"); )cc");
return; return;
default: default:
p.Emit(R"cc(return msg->$field_name$();)cc"); field.Emit(R"cc(return msg->$field_name$();)cc");
} }
}}, }},
{"setter_thunk_name", {"setter_thunk_name", GetAccessorThunkName(field, "set")},
GetAccessorThunkName(field, "set", underscore_delimited_full_name)},
{"setter_params", {"setter_params",
[&] { [&] {
switch (field->type()) { switch (field.desc().type()) {
case FieldDescriptor::TYPE_BYTES: case FieldDescriptor::TYPE_BYTES:
p.Emit("const char* ptr, size_t size"); field.Emit("const char* ptr, size_t size");
return; return;
default: default:
p.Emit({{"type_name", type_name}}, "$type_name$ val"); field.Emit({{"type_name", type_name}}, "$type_name$ val");
} }
}}, }},
{"setter_args", {"setter_args",
[&] { [&] {
switch (field->type()) { switch (field.desc().type()) {
case FieldDescriptor::TYPE_BYTES: case FieldDescriptor::TYPE_BYTES:
p.Emit("absl::string_view(ptr, size)"); field.Emit("absl::string_view(ptr, size)");
return; return;
default: default:
p.Emit("val"); field.Emit("val");
} }
}}, }},
{"clearer_thunk_name", {"clearer_thunk_name", GetAccessorThunkName(field, "clear")}},
GetAccessorThunkName(field, "clear",
underscore_delimited_full_name)}},
R"cc( R"cc(
extern "C" { extern "C" {
bool $hazzer_thunk_name$($namespace$::$Msg$* msg) { bool $hazzer_thunk_name$($namespace$::$Msg$* msg) {
@ -390,25 +260,16 @@ void GenerateAccessorThunksCcDefinitions(
} }
} }
void GenerateForCpp(const FileDescriptor* file, google::protobuf::io::Printer& p) { void GenerateForCpp(Context<FileDescriptor> file) {
for (int i = 0; i < file->message_type_count(); ++i) { for (int i = 0; i < file.desc().message_type_count(); ++i) {
const Descriptor* msg = file->message_type(i); auto msg = file.WithDesc(file.desc().message_type(i));
std::string underscore_delimited_full_name = msg.Emit(
GetUnderscoreDelimitedFullName(msg);
p.Emit(
{ {
{"Msg", msg->name()}, {"Msg", msg.desc().name()},
{"pkg_Msg", underscore_delimited_full_name}, {"pkg_Msg", GetUnderscoreDelimitedFullName(msg)},
{"accessor_fns", {"accessor_fns", [&] { GenerateAccessorFns(msg); }},
[&] {
GenerateAccessorFns(file->message_type(i), p,
underscore_delimited_full_name);
}},
{"accessor_thunks", {"accessor_thunks",
[&] { [&] { GenerateAccessorThunkRsDeclarations(msg); }},
GenerateAccessorThunkRsDeclarations(
file->message_type(i), p, underscore_delimited_full_name);
}},
}, },
R"rs( R"rs(
#[allow(non_camel_case_types)] #[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) { void GenerateThunksForCpp(Context<FileDescriptor> file) {
for (int i = 0; i < file->message_type_count(); ++i) { for (int i = 0; i < file.desc().message_type_count(); ++i) {
const Descriptor* msg = file->message_type(i); auto msg = file.WithDesc(file.desc().message_type(i));
std::string underscore_delimited_full_name = file.Emit(
GetUnderscoreDelimitedFullName(msg);
p.Emit(
{ {
{"Msg", msg->name()}, {"Msg", msg.desc().name()},
{"pkg_Msg", GetUnderscoreDelimitedFullName(msg)}, {"pkg_Msg", GetUnderscoreDelimitedFullName(msg)},
{"namespace", cpp::Namespace(msg)}, {"namespace", cpp::Namespace(&msg.desc())},
{"accessor_thunks", {"accessor_thunks",
[&] { [&] { GenerateAccessorThunksCcDefinitions(msg); }},
GenerateAccessorThunksCcDefinitions(
file->message_type(i), p, underscore_delimited_full_name);
}},
}, },
R"cc( R"cc(
extern "C" { extern "C" {
@ -499,78 +355,67 @@ std::string GetKernelRustName(Kernel kernel) {
return ""; return "";
} }
bool RustGenerator::Generate(const FileDescriptor* file, bool RustGenerator::Generate(const FileDescriptor* file_desc,
const std::string& parameter, const std::string& parameter,
GeneratorContext* generator_context, GeneratorContext* generator_context,
std::string* error) const { std::string* error) const {
std::vector<std::pair<std::string, std::string>> options; absl::StatusOr<Options> opts = Options::Parse(parameter);
ParseGeneratorParameter(parameter, &options); if (!opts.ok()) {
*error = std::string(opts.status().message());
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'.";
return false; return false;
} }
absl::optional<Kernel> kernel = ParseKernelConfiguration(options); Context<FileDescriptor> file(&*opts, file_desc, nullptr);
if (!kernel.has_value()) {
*error =
"Mandatory option `kernel` missing, please specify `cpp` or "
"`upb`.";
return false;
}
auto basename = StripProto(file->name()); auto outfile = absl::WrapUnique(generator_context->Open(GetRsFile(file)));
auto outfile = absl::WrapUnique(generator_context->Open( io::Printer printer(outfile.get());
absl::StrCat(basename, GetFileExtensionForKernel(*kernel)))); file = file.WithPrinter(&printer);
google::protobuf::io::Printer p(outfile.get()); file.Emit({{"kernel", GetKernelRustName(opts->kernel)}}, R"rs(
p.Emit({{"kernel", GetKernelRustName(*kernel)}}, R"rs(
extern crate protobuf_$kernel$ as __pb; extern crate protobuf_$kernel$ as __pb;
extern crate std as __std; extern crate std as __std;
)rs"); )rs");
EmitOpeningOfPackageModules(file->package(), p); EmitOpeningOfPackageModules(file);
// TODO(b/270124215): Delete the following "placeholder impl" of `import // TODO(b/270124215): Delete the following "placeholder impl" of `import
// public`. Also make sure to figure out how to map FileDescriptor#name to // public`. Also make sure to figure out how to map FileDescriptor#name to
// Rust crate names (currently Bazel labels). // Rust crate names (currently Bazel labels).
for (int i = 0; i < file->public_dependency_count(); ++i) { for (int i = 0; i < file.desc().public_dependency_count(); ++i) {
const FileDescriptor* dep = file->public_dependency(i); auto dep = file.WithDesc(file.desc().public_dependency(i));
std::string crate_name = GetCrateName(dep); 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 // TODO(b/272728844): Implement real logic
p.Emit( file.Emit({{"crate", crate_name},
{{"crate", crate_name}, {"pkg::Msg", GetCrateRelativeQualifiedPath(msg)}},
{"pkg::Msg", GetCrateRelativeQualifiedPath(dep->message_type(j))}},
R"rs( R"rs(
pub use $crate$::$pkg::Msg$; pub use $crate$::$pkg::Msg$;
)rs"); )rs");
} }
} }
switch (*kernel) { switch (opts->kernel) {
case Kernel::kUpb: case Kernel::kUpb:
rust::UpbKernel k; rust::UpbKernel().Generate(&file.desc(), printer);
k.Generate(file, p);
break; break;
case Kernel::kCpp: case Kernel::kCpp:
GenerateForCpp(file, p); GenerateForCpp(file);
auto thunksfile = absl::WrapUnique( auto thunksfile =
generator_context->Open(absl::StrCat(basename, ".pb.thunks.cc"))); absl::WrapUnique(generator_context->Open(GetThunkCcFile(file)));
google::protobuf::io::Printer thunks(thunksfile.get()); google::protobuf::io::Printer thunks(thunksfile.get());
thunks.Emit({{"basename", basename}}, auto thunk_file = file.WithPrinter(&thunks);
thunk_file.Emit({{"proto_h", GetHeaderFile(file)}},
R"cc( R"cc(
#include "$basename$.pb.h" #include "$proto_h$"
#include "google/protobuf/rust/cpp_kernel/cpp_api.h" #include "google/protobuf/rust/cpp_kernel/cpp_api.h"
)cc"); )cc");
GenerateThunksForCpp(file, thunks); GenerateThunksForCpp(thunk_file);
break; break;
} }
EmitClosingOfPackageModules(file->package(), p); EmitClosingOfPackageModules(file);
return true; return true;
} }

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