1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2023 Google LLC. All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7
8 #include "google/protobuf/compiler/rust/generator.h"
9
10 #include <memory>
11 #include <string>
12 #include <vector>
13
14 #include "absl/algorithm/container.h"
15 #include "absl/container/flat_hash_map.h"
16 #include "absl/container/flat_hash_set.h"
17 #include "absl/memory/memory.h"
18 #include "absl/status/status.h"
19 #include "absl/status/statusor.h"
20 #include "absl/strings/string_view.h"
21 #include "absl/types/span.h"
22 #include "google/protobuf/compiler/code_generator.h"
23 #include "google/protobuf/compiler/cpp/names.h"
24 #include "google/protobuf/compiler/rust/context.h"
25 #include "google/protobuf/compiler/rust/crate_mapping.h"
26 #include "google/protobuf/compiler/rust/enum.h"
27 #include "google/protobuf/compiler/rust/message.h"
28 #include "google/protobuf/compiler/rust/naming.h"
29 #include "google/protobuf/compiler/rust/relative_path.h"
30 #include "google/protobuf/descriptor.h"
31 #include "google/protobuf/descriptor.pb.h"
32 #include "google/protobuf/io/printer.h"
33
34 namespace google {
35 namespace protobuf {
36 namespace compiler {
37 namespace rust {
38 namespace {
39
40 // Emits `pub use <internal submodule name>::Type` for all messages and enums of
41 // a `non_primary_src` into the `primary_file`.
42 //
43 // `non_primary_src` has to be a non-primary src of the current `proto_library`.
EmitPubUseOfOwnTypes(Context & ctx,const FileDescriptor & primary_file,const FileDescriptor & non_primary_src)44 void EmitPubUseOfOwnTypes(Context& ctx, const FileDescriptor& primary_file,
45 const FileDescriptor& non_primary_src) {
46 auto mod = RustInternalModuleName(ctx, non_primary_src);
47 ctx.Emit({{"mod", mod}}, R"rs(
48 #[allow(unused_imports)]
49 pub use crate::$mod$::*;
50 )rs");
51 }
52
53 // Emits `pub use <crate_name>::<modules for parent types>::Type` for all
54 // messages and enums of a `dep`. This should only be
55 // called for 'import public' deps.
EmitPublicImportsForDepFile(Context & ctx,const FileDescriptor * dep)56 void EmitPublicImportsForDepFile(Context& ctx, const FileDescriptor* dep) {
57 std::string crate_name = GetCrateName(ctx, *dep);
58 for (int i = 0; i < dep->message_type_count(); ++i) {
59 auto* msg = dep->message_type(i);
60 auto path = GetCrateRelativeQualifiedPath(ctx, *msg);
61 ctx.Emit({{"crate", crate_name}, {"pkg::Msg", path}},
62 R"rs(
63 pub use $crate$::$pkg::Msg$;
64 pub use $crate$::$pkg::Msg$View;
65 pub use $crate$::$pkg::Msg$Mut;
66 )rs");
67 }
68 for (int i = 0; i < dep->enum_type_count(); ++i) {
69 auto* enum_ = dep->enum_type(i);
70 auto path = GetCrateRelativeQualifiedPath(ctx, *enum_);
71 ctx.Emit({{"crate", crate_name}, {"pkg::Enum", path}},
72 R"rs(
73 pub use $crate$::$pkg::Enum$;
74 )rs");
75 }
76 }
77
78 // Emits public imports of all files coming from dependencies (imports of local
79 // files are implicitly public).
80 //
81 // `import public` works transitively in C++ (although it doesn't respect
82 // layering_check in clang). For Rust we actually make it layering clean because
83 // Blaze compiles transitive proto deps as if they were direct.
84 //
85 // Note we don't reexport entire crates, only messages and enums from files that
86 // have been explicitly publicly imported. It may happen that a `proto_library`
87 // defines multiple files, but not all are publicly imported.
EmitPublicImports(Context & ctx,const std::vector<const FileDescriptor * > & srcs)88 void EmitPublicImports(Context& ctx,
89 const std::vector<const FileDescriptor*>& srcs) {
90 absl::flat_hash_set<const FileDescriptor*> files_in_current_target(
91 srcs.begin(), srcs.end());
92 std::vector<const FileDescriptor*> files_to_visit(srcs.begin(), srcs.end());
93 absl::c_reverse(files_to_visit);
94 while (!files_to_visit.empty()) {
95 const FileDescriptor* file = files_to_visit.back();
96 files_to_visit.pop_back();
97
98 if (!files_in_current_target.contains(file)) {
99 EmitPublicImportsForDepFile(ctx, file);
100 }
101
102 for (int i = 0; i < file->public_dependency_count(); ++i) {
103 files_to_visit.push_back(file->dependency(i));
104 }
105 }
106 }
107
108 // Emits submodule declarations so `rustc` can find non primary sources from
109 // the primary file.
DeclareSubmodulesForNonPrimarySrcs(Context & ctx,const FileDescriptor & primary_file,absl::Span<const FileDescriptor * const> non_primary_srcs)110 void DeclareSubmodulesForNonPrimarySrcs(
111 Context& ctx, const FileDescriptor& primary_file,
112 absl::Span<const FileDescriptor* const> non_primary_srcs) {
113 std::string primary_file_path = GetRsFile(ctx, primary_file);
114 RelativePath primary_relpath(primary_file_path);
115 for (const FileDescriptor* non_primary_src : non_primary_srcs) {
116 std::string non_primary_file_path = GetRsFile(ctx, *non_primary_src);
117 std::string relative_mod_path =
118 primary_relpath.Relative(RelativePath(non_primary_file_path));
119 ctx.Emit({{"file_path", relative_mod_path},
120 {"mod_name", RustInternalModuleName(ctx, *non_primary_src)}},
121 R"rs(
122 #[path="$file_path$"]
123 #[allow(non_snake_case)]
124 pub mod $mod_name$;
125 )rs");
126 }
127 }
128
129 // Emits `pub use <...>::Msg` for all messages in non primary sources.
ReexportMessagesFromSubmodules(Context & ctx,const FileDescriptor & primary_file,absl::Span<const FileDescriptor * const> non_primary_srcs)130 void ReexportMessagesFromSubmodules(
131 Context& ctx, const FileDescriptor& primary_file,
132 absl::Span<const FileDescriptor* const> non_primary_srcs) {
133 for (const FileDescriptor* file : non_primary_srcs) {
134 EmitPubUseOfOwnTypes(ctx, primary_file, *file);
135 }
136 }
137
138 } // namespace
139
Generate(const FileDescriptor * file,const std::string & parameter,GeneratorContext * generator_context,std::string * error) const140 bool RustGenerator::Generate(const FileDescriptor* file,
141 const std::string& parameter,
142 GeneratorContext* generator_context,
143 std::string* error) const {
144 absl::StatusOr<Options> opts = Options::Parse(parameter);
145 if (!opts.ok()) {
146 *error = std::string(opts.status().message());
147 return false;
148 }
149
150 std::vector<const FileDescriptor*> files_in_current_crate;
151 generator_context->ListParsedFiles(&files_in_current_crate);
152
153 absl::StatusOr<absl::flat_hash_map<std::string, std::string>>
154 import_path_to_crate_name = GetImportPathToCrateNameMap(&*opts);
155 if (!import_path_to_crate_name.ok()) {
156 *error = std::string(import_path_to_crate_name.status().message());
157 return false;
158 }
159
160 RustGeneratorContext rust_generator_context(&files_in_current_crate,
161 &*import_path_to_crate_name);
162
163 Context ctx_without_printer(&*opts, &rust_generator_context, nullptr);
164
165 auto outfile = absl::WrapUnique(
166 generator_context->Open(GetRsFile(ctx_without_printer, *file)));
167 io::Printer printer(outfile.get());
168 Context ctx = ctx_without_printer.WithPrinter(&printer);
169
170 // Convenience shorthands for common symbols.
171 auto v = ctx.printer().WithVars({
172 {"std", "::__std"},
173 {"pb", "::__pb"},
174 {"pbi", "::__pb::__internal"},
175 {"pbr", "::__pb::__runtime"},
176 {"NonNull", "::__std::ptr::NonNull"},
177 {"Phantom", "::__std::marker::PhantomData"},
178 {"Result", "::__std::result::Result"},
179 {"Option", "::__std::option::Option"},
180 });
181
182 ctx.Emit({{"kernel", KernelRsName(ctx.opts().kernel)}}, R"rs(
183 extern crate protobuf_$kernel$ as __pb;
184 extern crate std as __std;
185
186 )rs");
187
188 std::vector<const FileDescriptor*> file_contexts(
189 files_in_current_crate.begin(), files_in_current_crate.end());
190
191 // Generating the primary file?
192 if (file == &rust_generator_context.primary_file()) {
193 auto non_primary_srcs = absl::MakeConstSpan(file_contexts).subspan(1);
194 DeclareSubmodulesForNonPrimarySrcs(ctx, *file, non_primary_srcs);
195 ReexportMessagesFromSubmodules(ctx, *file, non_primary_srcs);
196 EmitPublicImports(ctx, file_contexts);
197 }
198
199 std::unique_ptr<io::ZeroCopyOutputStream> thunks_cc;
200 std::unique_ptr<io::Printer> thunks_printer;
201 if (ctx.is_cpp()) {
202 thunks_cc.reset(generator_context->Open(GetThunkCcFile(ctx, *file)));
203 thunks_printer = std::make_unique<io::Printer>(thunks_cc.get());
204
205 thunks_printer->Emit(
206 {{"proto_h", GetHeaderFile(ctx, *file)},
207 {"proto_deps_h",
208 [&] {
209 for (int i = 0; i < file->dependency_count(); i++) {
210 if (opts->strip_nonfunctional_codegen &&
211 IsKnownFeatureProto(file->dependency(i)->name())) {
212 // Strip feature imports for editions codegen tests.
213 continue;
214 }
215 thunks_printer->Emit(
216 {{"proto_dep_h", GetHeaderFile(ctx, *file->dependency(i))}},
217 R"cc(
218 #include "$proto_dep_h$"
219 )cc");
220 }
221 }}},
222 R"cc(
223 #include "$proto_h$"
224 $proto_deps_h$
225 #include "google/protobuf/map.h"
226 #include "google/protobuf/repeated_field.h"
227 #include "google/protobuf/repeated_ptr_field.h"
228 #include "rust/cpp_kernel/map.h"
229 #include "rust/cpp_kernel/serialized_data.h"
230 #include "rust/cpp_kernel/strings.h"
231 )cc");
232 }
233
234 for (int i = 0; i < file->message_type_count(); ++i) {
235 auto& msg = *file->message_type(i);
236
237 GenerateRs(ctx, msg);
238 ctx.printer().PrintRaw("\n");
239
240 if (ctx.is_cpp()) {
241 auto thunks_ctx = ctx.WithPrinter(thunks_printer.get());
242
243 thunks_ctx.Emit({{"Msg", msg.full_name()}}, R"cc(
244 // $Msg$
245 )cc");
246 GenerateThunksCc(thunks_ctx, msg);
247 thunks_ctx.printer().PrintRaw("\n");
248 }
249 }
250
251 for (int i = 0; i < file->enum_type_count(); ++i) {
252 auto& enum_ = *file->enum_type(i);
253 GenerateEnumDefinition(ctx, enum_);
254 ctx.printer().PrintRaw("\n");
255
256 if (ctx.is_cpp()) {
257 auto thunks_ctx = ctx.WithPrinter(thunks_printer.get());
258
259 thunks_ctx.Emit({{"enum", enum_.full_name()}}, R"cc(
260 // $enum$
261 )cc");
262 thunks_ctx.printer().PrintRaw("\n");
263 }
264 }
265
266 return true;
267 }
268
269 } // namespace rust
270 } // namespace compiler
271 } // namespace protobuf
272 } // namespace google
273