• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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