diff options
-rw-r--r-- | .cargo_vcs_info.json | 2 | ||||
-rw-r--r-- | 2.27.1/.cargo_vcs_info.json | 6 | ||||
-rw-r--r-- | 2.27.1/Android.bp | 67 | ||||
-rw-r--r-- | 2.27.1/Cargo.lock | 16 | ||||
-rw-r--r-- | 2.27.1/Cargo.toml | 44 | ||||
-rw-r--r-- | 2.27.1/Cargo.toml.orig | 35 | ||||
l--------- | 2.27.1/LICENSE | 1 | ||||
-rw-r--r-- | 2.27.1/LICENSE.txt | 19 | ||||
-rw-r--r-- | 2.27.1/METADATA | 19 | ||||
-rw-r--r-- | 2.27.1/MODULE_LICENSE_MIT | 0 | ||||
l--------- | 2.27.1/NOTICE | 1 | ||||
-rw-r--r-- | 2.27.1/OWNERS | 1 | ||||
-rw-r--r-- | 2.27.1/README.md | 80 | ||||
-rw-r--r-- | 2.27.1/cargo2android.json | 4 | ||||
-rw-r--r-- | 2.27.1/src/bin/protobuf-bin-gen-rust-do-not-use.rs (renamed from src/bin/protobuf-bin-gen-rust-do-not-use.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/bin/protoc-gen-rust.rs | 5 | ||||
-rw-r--r-- | 2.27.1/src/code_writer.rs (renamed from src/code_writer.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/customize.rs (renamed from src/customize.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/enums.rs (renamed from src/enums.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/extensions.rs (renamed from src/extensions.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/field/mod.rs (renamed from src/field/mod.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/file.rs (renamed from src/file.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/file_and_mod.rs (renamed from src/file_and_mod.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/file_descriptor.rs (renamed from src/file_descriptor.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/float.rs (renamed from src/float.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/inside.rs (renamed from src/inside.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/lib.rs | 387 | ||||
-rw-r--r-- | 2.27.1/src/message.rs (renamed from src/message.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/oneof.rs (renamed from src/oneof.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/protobuf_name.rs (renamed from src/protobuf_name.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/rust.rs (renamed from src/rust.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/rust_name.rs (renamed from src/rust_name.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/rust_types_values.rs (renamed from src/rust_types_values.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/scope.rs (renamed from src/scope.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/serde.rs (renamed from src/serde.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/strx.rs (renamed from src/strx.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/syntax.rs (renamed from src/syntax.rs) | 0 | ||||
-rw-r--r-- | 2.27.1/src/well_known_types.rs (renamed from src/well_known_types.rs) | 0 | ||||
-rw-r--r-- | Android.bp | 34 | ||||
-rw-r--r-- | Cargo.lock | 279 | ||||
-rw-r--r-- | Cargo.toml | 31 | ||||
-rw-r--r-- | Cargo.toml.orig | 22 | ||||
-rw-r--r-- | METADATA | 14 | ||||
-rw-r--r-- | README.md | 105 | ||||
-rw-r--r-- | src/bin/protoc-gen-rust.rs | 4 | ||||
-rw-r--r-- | src/codegen/mod.rs | 272 | ||||
-rw-r--r-- | src/compiler_plugin.rs | 48 | ||||
-rw-r--r-- | src/customize/ctx.rs | 92 | ||||
-rw-r--r-- | src/customize/mod.rs | 251 | ||||
-rw-r--r-- | src/customize/rustproto_proto.rs | 74 | ||||
-rw-r--r-- | src/gen/all.rs | 64 | ||||
-rw-r--r-- | src/gen/code_writer.rs | 455 | ||||
-rw-r--r-- | src/gen/descriptor.rs | 68 | ||||
-rw-r--r-- | src/gen/enums.rs | 413 | ||||
-rw-r--r-- | src/gen/extensions.rs | 133 | ||||
-rw-r--r-- | src/gen/field/accessor.rs | 241 | ||||
-rw-r--r-- | src/gen/field/elem.rs | 319 | ||||
-rw-r--r-- | src/gen/field/mod.rs | 1850 | ||||
-rw-r--r-- | src/gen/field/option_kind.rs | 59 | ||||
-rw-r--r-- | src/gen/field/repeated.rs | 46 | ||||
-rw-r--r-- | src/gen/field/singular.rs | 52 | ||||
-rw-r--r-- | src/gen/field/tag.rs | 5 | ||||
-rw-r--r-- | src/gen/field/type_ext.rs | 115 | ||||
-rw-r--r-- | src/gen/file.rs | 149 | ||||
-rw-r--r-- | src/gen/file_and_mod.rs | 8 | ||||
-rw-r--r-- | src/gen/file_descriptor.rs | 208 | ||||
-rw-r--r-- | src/gen/inside.rs | 11 | ||||
-rw-r--r-- | src/gen/map.rs | 16 | ||||
-rw-r--r-- | src/gen/message.rs | 786 | ||||
-rw-r--r-- | src/gen/mod.rs | 21 | ||||
-rw-r--r-- | src/gen/mod_rs.rs | 18 | ||||
-rw-r--r-- | src/gen/oneof.rs | 373 | ||||
-rw-r--r-- | src/gen/paths.rs | 109 | ||||
-rw-r--r-- | src/gen/protoc_insertion_point.rs | 80 | ||||
-rw-r--r-- | src/gen/rust/component.rs | 34 | ||||
-rw-r--r-- | src/gen/rust/ident.rs | 62 | ||||
-rw-r--r-- | src/gen/rust/ident_with_path.rs | 45 | ||||
-rw-r--r-- | src/gen/rust/keywords.rs | 86 | ||||
-rw-r--r-- | src/gen/rust/mod.rs | 8 | ||||
-rw-r--r-- | src/gen/rust/path.rs | 99 | ||||
-rw-r--r-- | src/gen/rust/quote.rs | 58 | ||||
-rw-r--r-- | src/gen/rust/rel_path.rs | 97 | ||||
-rw-r--r-- | src/gen/rust/snippets.rs | 10 | ||||
-rw-r--r-- | src/gen/rust_types_values.rs | 617 | ||||
-rw-r--r-- | src/gen/scope.rs | 536 | ||||
-rw-r--r-- | src/gen/strx.rs | 57 | ||||
-rw-r--r-- | src/gen/well_known_types.rs | 123 | ||||
-rw-r--r-- | src/gen_and_write.rs | 62 | ||||
-rw-r--r-- | src/lib.rs | 434 | ||||
-rw-r--r-- | src/protoc_gen_rust.rs | 21 |
90 files changed, 9457 insertions, 404 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 8ec5394..0cfc90b 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "ec31ce829473039ac598ca6fdcb245cbd6fa82ba" + "sha1": "7155092f3df112159d55132081937e1fe5c30490" }, "path_in_vcs": "protobuf-codegen" }
\ No newline at end of file diff --git a/2.27.1/.cargo_vcs_info.json b/2.27.1/.cargo_vcs_info.json new file mode 100644 index 0000000..8ec5394 --- /dev/null +++ b/2.27.1/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "ec31ce829473039ac598ca6fdcb245cbd6fa82ba" + }, + "path_in_vcs": "protobuf-codegen" +}
\ No newline at end of file diff --git a/2.27.1/Android.bp b/2.27.1/Android.bp new file mode 100644 index 0000000..e705500 --- /dev/null +++ b/2.27.1/Android.bp @@ -0,0 +1,67 @@ +// This file is generated by cargo2android.py --config cargo2android.json. +// Do not modify this file as changes will be overridden on upgrade. + +package { + default_applicable_licenses: [ + "external_rust_crates_protobuf-codegen_2.27.1_license", + ], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "external_rust_crates_protobuf-codegen_2.27.1_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-MIT", + ], + license_text: [ + "LICENSE.txt", + ], +} + +rust_library_host { + name: "libprotobuf_codegen_deprecated", + crate_name: "protobuf_codegen", + cargo_env_compat: true, + cargo_pkg_version: "2.27.1", + srcs: ["src/lib.rs"], + edition: "2015", + rustlibs: [ + "libprotobuf_deprecated", + ], + product_available: true, + vendor_available: true, +} + +rust_test_host { + name: "protobuf-codegen_deprecated_test_src_lib", + crate_name: "protobuf_codegen", + cargo_env_compat: true, + cargo_pkg_version: "2.27.1", + srcs: ["src/lib.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2015", + rustlibs: [ + "libprotobuf_deprecated", + ], +} + +rust_binary_host { + name: "protoc-gen-rust-deprecated", + crate_name: "protoc_gen_rust", + cargo_env_compat: true, + cargo_pkg_version: "2.27.1", + srcs: ["src/bin/protoc-gen-rust.rs"], + edition: "2015", + rustlibs: [ + "libprotobuf_deprecated", + "libprotobuf_codegen_deprecated", + ], + product_available: true, + vendor_available: true, +} diff --git a/2.27.1/Cargo.lock b/2.27.1/Cargo.lock new file mode 100644 index 0000000..e9b0703 --- /dev/null +++ b/2.27.1/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "protobuf" +version = "2.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" + +[[package]] +name = "protobuf-codegen" +version = "2.27.1" +dependencies = [ + "protobuf", +] diff --git a/2.27.1/Cargo.toml b/2.27.1/Cargo.toml new file mode 100644 index 0000000..ca327e2 --- /dev/null +++ b/2.27.1/Cargo.toml @@ -0,0 +1,44 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +name = "protobuf-codegen" +version = "2.27.1" +authors = ["Stepan Koltsov <stepan.koltsov@gmail.com>"] +description = """ +Code generator for rust-protobuf. + +Includes a library and `protoc-gen-rust` binary. + +See `protoc-rust` and `protobuf-codegen-pure` crates. +""" +homepage = "https://github.com/stepancheg/rust-protobuf/" +license = "MIT" +repository = "https://github.com/stepancheg/rust-protobuf/" + +[package.metadata.docs.rs] +all-features = true + +[lib] +bench = false + +[[bin]] +name = "protoc-gen-rust" +path = "src/bin/protoc-gen-rust.rs" +test = false + +[[bin]] +name = "protobuf-bin-gen-rust-do-not-use" +path = "src/bin/protobuf-bin-gen-rust-do-not-use.rs" +test = false + +[dependencies.protobuf] +version = "=2.27.1" diff --git a/2.27.1/Cargo.toml.orig b/2.27.1/Cargo.toml.orig new file mode 100644 index 0000000..3400e66 --- /dev/null +++ b/2.27.1/Cargo.toml.orig @@ -0,0 +1,35 @@ +[package] +name = "protobuf-codegen" +version = "2.27.1" +authors = ["Stepan Koltsov <stepan.koltsov@gmail.com>"] +license = "MIT" +homepage = "https://github.com/stepancheg/rust-protobuf/" +repository = "https://github.com/stepancheg/rust-protobuf/" +description = """ +Code generator for rust-protobuf. + +Includes a library and `protoc-gen-rust` binary. + +See `protoc-rust` and `protobuf-codegen-pure` crates. +""" + +[lib] +bench = false + +[dependencies] +protobuf = { path = "../protobuf", version = "=2.27.1" } + +[[bin]] + +name = "protoc-gen-rust" +path = "src/bin/protoc-gen-rust.rs" +test = false + +[[bin]] + +name = "protobuf-bin-gen-rust-do-not-use" +path = "src/bin/protobuf-bin-gen-rust-do-not-use.rs" +test = false + +[package.metadata.docs.rs] +all-features = true diff --git a/2.27.1/LICENSE b/2.27.1/LICENSE new file mode 120000 index 0000000..85de3d4 --- /dev/null +++ b/2.27.1/LICENSE @@ -0,0 +1 @@ +LICENSE.txt
\ No newline at end of file diff --git a/2.27.1/LICENSE.txt b/2.27.1/LICENSE.txt new file mode 100644 index 0000000..acce639 --- /dev/null +++ b/2.27.1/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2019 Stepan Koltsov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file diff --git a/2.27.1/METADATA b/2.27.1/METADATA new file mode 100644 index 0000000..4593845 --- /dev/null +++ b/2.27.1/METADATA @@ -0,0 +1,19 @@ +name: "protobuf-codegen" +description: "Code generator for rust-protobuf. Includes a library and `protoc-gen-rust` binary. See `protoc-rust` and `protobuf-codegen-pure` crates." +third_party { + url { + type: HOMEPAGE + value: "https://crates.io/crates/protobuf-codegen" + } + url { + type: ARCHIVE + value: "https://static.crates.io/crates/protobuf-codegen/protobuf-codegen-2.27.1.crate" + } + version: "2.27.1" + license_type: NOTICE + last_upgrade_date { + year: 2022 + month: 3 + day: 1 + } +} diff --git a/2.27.1/MODULE_LICENSE_MIT b/2.27.1/MODULE_LICENSE_MIT new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/2.27.1/MODULE_LICENSE_MIT diff --git a/2.27.1/NOTICE b/2.27.1/NOTICE new file mode 120000 index 0000000..7a694c9 --- /dev/null +++ b/2.27.1/NOTICE @@ -0,0 +1 @@ +LICENSE
\ No newline at end of file diff --git a/2.27.1/OWNERS b/2.27.1/OWNERS new file mode 100644 index 0000000..46fc303 --- /dev/null +++ b/2.27.1/OWNERS @@ -0,0 +1 @@ +include platform/prebuilts/rust:/OWNERS diff --git a/2.27.1/README.md b/2.27.1/README.md new file mode 100644 index 0000000..2ab7e05 --- /dev/null +++ b/2.27.1/README.md @@ -0,0 +1,80 @@ +<!-- cargo-sync-readme start --> + +# Protobuf code generator + +This crate contains protobuf code generator implementation +and a `protoc-gen-rust` `protoc` plugin. + +This crate: +* provides `protoc-gen-rust` plugin for `protoc` command +* implement protobuf codegen + +This crate is not meant to be used directly, in fact, it does not provide any public API +(except for `protoc-gen-rust` binary). + +Code can be generated with either: +* `protoc-gen-rust` plugin for `protoc` or +* [`protoc-rust`](https://docs.rs/protoc) crate + (code generator which depends on `protoc` binary for parsing of `.proto` files) +* [`protobuf-codegen-pure`](https://docs.rs/protobuf-codegen-pure) crate, + similar API to `protoc-rust`, but uses pure rust parser of `.proto` files. + +# `protoc-gen-rust` plugin for `protoc` + +When non-cargo build system is used, consider using standard protobuf code generation pattern: +`protoc` command does all the work of handling paths and parsing `.proto` files. +When `protoc` is invoked with `--rust_out=` option, it invokes `protoc-gen-rust` plugin. +provided by this crate. + +When building with cargo, consider using `protoc-rust` or `protobuf-codegen-pure` crates. + +## How to use `protoc-gen-rust` if you have to + +(Note `protoc` can be invoked programmatically with +[protoc crate](https://docs.rs/protoc)) + +0) Install protobuf for `protoc` binary. + +On OS X [Homebrew](https://github.com/Homebrew/brew) can be used: + +```sh +brew install protobuf +``` + +On Ubuntu, `protobuf-compiler` package can be installed: + +```sh +apt-get install protobuf-compiler +``` + +Protobuf is needed only for code generation, `rust-protobuf` runtime +does not use `protobuf` library. + +1) Install `protoc-gen-rust` program (which is `protoc` plugin) + +It can be installed either from source or with `cargo install protobuf` command. + +2) Add `protoc-gen-rust` to $PATH + +If you installed it with cargo, it should be + +```sh +PATH="$HOME/.cargo/bin:$PATH" +``` + +3) Generate .rs files: + +```sh +protoc --rust_out . foo.proto +``` + +This will generate .rs files in current directory. + +# Version 2 + +This is documentation for version 2 of the crate. + +[Version 3 of the crate](https://docs.rs/protobuf-codegen/%3E=3.0.0-alpha) +(currently in development) encapsulates both `protoc` and pure codegens in this crate. + +<!-- cargo-sync-readme end --> diff --git a/2.27.1/cargo2android.json b/2.27.1/cargo2android.json new file mode 100644 index 0000000..341300b --- /dev/null +++ b/2.27.1/cargo2android.json @@ -0,0 +1,4 @@ +{ + "run": true, + "tests": true +}
\ No newline at end of file diff --git a/src/bin/protobuf-bin-gen-rust-do-not-use.rs b/2.27.1/src/bin/protobuf-bin-gen-rust-do-not-use.rs index a6f96ca..a6f96ca 100644 --- a/src/bin/protobuf-bin-gen-rust-do-not-use.rs +++ b/2.27.1/src/bin/protobuf-bin-gen-rust-do-not-use.rs diff --git a/2.27.1/src/bin/protoc-gen-rust.rs b/2.27.1/src/bin/protoc-gen-rust.rs new file mode 100644 index 0000000..97ac7d2 --- /dev/null +++ b/2.27.1/src/bin/protoc-gen-rust.rs @@ -0,0 +1,5 @@ +extern crate protobuf_codegen; + +fn main() { + protobuf_codegen::protoc_gen_rust_main(); +} diff --git a/src/code_writer.rs b/2.27.1/src/code_writer.rs index 50eaeb8..50eaeb8 100644 --- a/src/code_writer.rs +++ b/2.27.1/src/code_writer.rs diff --git a/src/customize.rs b/2.27.1/src/customize.rs index b3415ab..b3415ab 100644 --- a/src/customize.rs +++ b/2.27.1/src/customize.rs diff --git a/src/enums.rs b/2.27.1/src/enums.rs index ec3444e..ec3444e 100644 --- a/src/enums.rs +++ b/2.27.1/src/enums.rs diff --git a/src/extensions.rs b/2.27.1/src/extensions.rs index def5948..def5948 100644 --- a/src/extensions.rs +++ b/2.27.1/src/extensions.rs diff --git a/src/field/mod.rs b/2.27.1/src/field/mod.rs index e4be944..e4be944 100644 --- a/src/field/mod.rs +++ b/2.27.1/src/field/mod.rs diff --git a/src/file.rs b/2.27.1/src/file.rs index 413e1bc..413e1bc 100644 --- a/src/file.rs +++ b/2.27.1/src/file.rs diff --git a/src/file_and_mod.rs b/2.27.1/src/file_and_mod.rs index 4f4e8c6..4f4e8c6 100644 --- a/src/file_and_mod.rs +++ b/2.27.1/src/file_and_mod.rs diff --git a/src/file_descriptor.rs b/2.27.1/src/file_descriptor.rs index dde8603..dde8603 100644 --- a/src/file_descriptor.rs +++ b/2.27.1/src/file_descriptor.rs diff --git a/src/float.rs b/2.27.1/src/float.rs index 78ca622..78ca622 100644 --- a/src/float.rs +++ b/2.27.1/src/float.rs diff --git a/src/inside.rs b/2.27.1/src/inside.rs index bbdcfdb..bbdcfdb 100644 --- a/src/inside.rs +++ b/2.27.1/src/inside.rs diff --git a/2.27.1/src/lib.rs b/2.27.1/src/lib.rs new file mode 100644 index 0000000..826e18b --- /dev/null +++ b/2.27.1/src/lib.rs @@ -0,0 +1,387 @@ +//! # Protobuf code generator +//! +//! This crate contains protobuf code generator implementation +//! and a `protoc-gen-rust` `protoc` plugin. +//! +//! This crate: +//! * provides `protoc-gen-rust` plugin for `protoc` command +//! * implement protobuf codegen +//! +//! This crate is not meant to be used directly, in fact, it does not provide any public API +//! (except for `protoc-gen-rust` binary). +//! +//! Code can be generated with either: +//! * `protoc-gen-rust` plugin for `protoc` or +//! * [`protoc-rust`](https://docs.rs/protoc) crate +//! (code generator which depends on `protoc` binary for parsing of `.proto` files) +//! * [`protobuf-codegen-pure`](https://docs.rs/protobuf-codegen-pure) crate, +//! similar API to `protoc-rust`, but uses pure rust parser of `.proto` files. +//! +//! # `protoc-gen-rust` plugin for `protoc` +//! +//! When non-cargo build system is used, consider using standard protobuf code generation pattern: +//! `protoc` command does all the work of handling paths and parsing `.proto` files. +//! When `protoc` is invoked with `--rust_out=` option, it invokes `protoc-gen-rust` plugin. +//! provided by this crate. +//! +//! When building with cargo, consider using `protoc-rust` or `protobuf-codegen-pure` crates. +//! +//! ## How to use `protoc-gen-rust` if you have to +//! +//! (Note `protoc` can be invoked programmatically with +//! [protoc crate](https://docs.rs/protoc)) +//! +//! 0) Install protobuf for `protoc` binary. +//! +//! On OS X [Homebrew](https://github.com/Homebrew/brew) can be used: +//! +//! ```sh +//! brew install protobuf +//! ``` +//! +//! On Ubuntu, `protobuf-compiler` package can be installed: +//! +//! ```sh +//! apt-get install protobuf-compiler +//! ``` +//! +//! Protobuf is needed only for code generation, `rust-protobuf` runtime +//! does not use `protobuf` library. +//! +//! 1) Install `protoc-gen-rust` program (which is `protoc` plugin) +//! +//! It can be installed either from source or with `cargo install protobuf` command. +//! +//! 2) Add `protoc-gen-rust` to $PATH +//! +//! If you installed it with cargo, it should be +//! +//! ```sh +//! PATH="$HOME/.cargo/bin:$PATH" +//! ``` +//! +//! 3) Generate .rs files: +//! +//! ```sh +//! protoc --rust_out . foo.proto +//! ``` +//! +//! This will generate .rs files in current directory. +//! +//! # Version 2 +//! +//! This is documentation for version 2 of the crate. +//! +//! [Version 3 of the crate](https://docs.rs/protobuf-codegen/%3E=3.0.0-alpha) +//! (currently in development) encapsulates both `protoc` and pure codegens in this crate. + +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(missing_docs)] + +extern crate protobuf; + +use std::collections::hash_map::HashMap; +use std::fmt::Write as FmtWrite; +use std::fs::File; +use std::io; +use std::io::Write; +use std::path::Path; + +use protobuf::compiler_plugin; +use protobuf::descriptor::*; +use protobuf::Message; + +mod customize; +mod enums; +mod extensions; +mod field; +mod file; +mod file_and_mod; +mod file_descriptor; +#[doc(hidden)] +pub mod float; +mod inside; +mod message; +mod oneof; +mod protobuf_name; +mod rust_name; +mod rust_types_values; +mod serde; +mod well_known_types; + +pub(crate) mod rust; +pub(crate) mod scope; +pub(crate) mod strx; +pub(crate) mod syntax; + +use customize::customize_from_rustproto_for_file; +#[doc(hidden)] +pub use customize::Customize; + +pub mod code_writer; + +use inside::protobuf_crate_path; +#[doc(hidden)] +pub use protobuf_name::ProtobufAbsolutePath; +#[doc(hidden)] +pub use protobuf_name::ProtobufIdent; +#[doc(hidden)] +pub use protobuf_name::ProtobufRelativePath; +use scope::FileScope; +use scope::RootScope; + +use self::code_writer::CodeWriter; +use self::enums::*; +use self::extensions::*; +use self::message::*; +use crate::file::proto_path_to_rust_mod; + +fn escape_byte(s: &mut String, b: u8) { + if b == b'\n' { + write!(s, "\\n").unwrap(); + } else if b == b'\r' { + write!(s, "\\r").unwrap(); + } else if b == b'\t' { + write!(s, "\\t").unwrap(); + } else if b == b'\\' || b == b'"' { + write!(s, "\\{}", b as char).unwrap(); + } else if b == b'\0' { + write!(s, "\\0").unwrap(); + // ASCII printable except space + } else if b > 0x20 && b < 0x7f { + write!(s, "{}", b as char).unwrap(); + } else { + write!(s, "\\x{:02x}", b).unwrap(); + } +} + +fn write_file_descriptor_data( + file: &FileDescriptorProto, + customize: &Customize, + w: &mut CodeWriter, +) { + let fdp_bytes = file.write_to_bytes().unwrap(); + w.write_line("static file_descriptor_proto_data: &'static [u8] = b\"\\"); + w.indented(|w| { + const MAX_LINE_LEN: usize = 72; + + let mut s = String::new(); + for &b in &fdp_bytes { + let prev_len = s.len(); + escape_byte(&mut s, b); + let truncate = s.len() > MAX_LINE_LEN; + if truncate { + s.truncate(prev_len); + } + if truncate || s.len() == MAX_LINE_LEN { + write!(s, "\\").unwrap(); + w.write_line(&s); + s.clear(); + } + if truncate { + escape_byte(&mut s, b); + } + } + if !s.is_empty() { + write!(s, "\\").unwrap(); + w.write_line(&s); + s.clear(); + } + }); + w.write_line("\";"); + w.write_line(""); + w.lazy_static( + "file_descriptor_proto_lazy", + &format!( + "{}::descriptor::FileDescriptorProto", + protobuf_crate_path(customize) + ), + customize, + ); + w.write_line(""); + w.def_fn( + &format!( + "parse_descriptor_proto() -> {}::descriptor::FileDescriptorProto", + protobuf_crate_path(customize) + ), + |w| { + w.write_line(&format!( + "{}::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()", + protobuf_crate_path(customize) + )); + }, + ); + w.write_line(""); + w.pub_fn( + &format!( + "file_descriptor_proto() -> &'static {}::descriptor::FileDescriptorProto", + protobuf_crate_path(customize) + ), + |w| { + w.block("file_descriptor_proto_lazy.get(|| {", "})", |w| { + w.write_line("parse_descriptor_proto()"); + }); + }, + ); +} + +struct GenFileResult { + compiler_plugin_result: compiler_plugin::GenResult, + mod_name: String, +} + +fn gen_file( + file: &FileDescriptorProto, + _files_map: &HashMap<&str, &FileDescriptorProto>, + root_scope: &RootScope, + customize: &Customize, +) -> GenFileResult { + // TODO: use it + let mut customize = customize.clone(); + // options specified in invocation have precedence over options specified in file + customize.update_with(&customize_from_rustproto_for_file(file.get_options())); + + let scope = FileScope { + file_descriptor: file, + } + .to_scope(); + let lite_runtime = customize.lite_runtime.unwrap_or_else(|| { + file.get_options().get_optimize_for() == FileOptions_OptimizeMode::LITE_RUNTIME + }); + + let mut v = Vec::new(); + + { + let mut w = CodeWriter::new(&mut v); + + w.write_generated_by("rust-protobuf", "2.27.1"); + w.write_line(&format!("//! Generated file from `{}`", file.get_name())); + if customize.inside_protobuf != Some(true) { + w.write_line(""); + w.write_line("/// Generated files are compatible only with the same version"); + w.write_line("/// of protobuf runtime."); + w.commented(|w| { + w.write_line(&format!( + "const _PROTOBUF_VERSION_CHECK: () = {}::{};", + protobuf_crate_path(&customize), + protobuf::VERSION_IDENT + )); + }) + } + + for message in &scope.get_messages() { + // ignore map entries, because they are not used in map fields + if message.map_entry().is_none() { + w.write_line(""); + MessageGen::new(message, &root_scope, &customize).write(&mut w); + } + } + for enum_type in &scope.get_enums() { + w.write_line(""); + EnumGen::new(enum_type, file, &customize, root_scope).write(&mut w); + } + + write_extensions(file, &root_scope, &mut w, &customize); + + if !lite_runtime { + w.write_line(""); + write_file_descriptor_data(file, &customize, &mut w); + } + } + + GenFileResult { + compiler_plugin_result: compiler_plugin::GenResult { + name: format!("{}.rs", proto_path_to_rust_mod(file.get_name())), + content: v, + }, + mod_name: proto_path_to_rust_mod(file.get_name()).into_string(), + } +} + +fn gen_mod_rs(mods: &[String]) -> compiler_plugin::GenResult { + let mut v = Vec::new(); + let mut w = CodeWriter::new(&mut v); + w.comment("@generated"); + w.write_line(""); + for m in mods { + w.write_line(&format!("pub mod {};", m)); + } + drop(w); + compiler_plugin::GenResult { + name: "mod.rs".to_owned(), + content: v, + } +} + +// This function is also used externally by cargo plugin +// https://github.com/plietar/rust-protobuf-build +// So be careful changing its signature. +#[doc(hidden)] +pub fn gen( + file_descriptors: &[FileDescriptorProto], + files_to_generate: &[String], + customize: &Customize, +) -> Vec<compiler_plugin::GenResult> { + let root_scope = RootScope { + file_descriptors: file_descriptors, + }; + + let mut results: Vec<compiler_plugin::GenResult> = Vec::new(); + let files_map: HashMap<&str, &FileDescriptorProto> = + file_descriptors.iter().map(|f| (f.get_name(), f)).collect(); + + let all_file_names: Vec<&str> = file_descriptors.iter().map(|f| f.get_name()).collect(); + + let mut mods = Vec::new(); + + for file_name in files_to_generate { + let file = files_map.get(&file_name[..]).expect(&format!( + "file not found in file descriptors: {:?}, files: {:?}", + file_name, all_file_names + )); + + let gen_file_result = gen_file(file, &files_map, &root_scope, customize); + results.push(gen_file_result.compiler_plugin_result); + mods.push(gen_file_result.mod_name); + } + + if customize.gen_mod_rs.unwrap_or(false) { + results.push(gen_mod_rs(&mods)); + } + + results +} + +#[doc(hidden)] +pub fn gen_and_write( + file_descriptors: &[FileDescriptorProto], + files_to_generate: &[String], + out_dir: &Path, + customize: &Customize, +) -> io::Result<()> { + let results = gen(file_descriptors, files_to_generate, customize); + + for r in &results { + let mut file_path = out_dir.to_owned(); + file_path.push(&r.name); + let mut file_writer = File::create(&file_path)?; + file_writer.write_all(&r.content)?; + file_writer.flush()?; + } + + Ok(()) +} + +#[doc(hidden)] +pub fn protoc_gen_rust_main() { + compiler_plugin::plugin_main_2(|r| { + let customize = Customize::parse_from_parameter(r.parameter).expect("parse options"); + gen(r.file_descriptors, r.files_to_generate, &customize) + }); +} + +/// Used in protobuf-codegen-identical-test +#[doc(hidden)] +pub fn proto_name_to_rs(name: &str) -> String { + format!("{}.rs", proto_path_to_rust_mod(name)) +} diff --git a/src/message.rs b/2.27.1/src/message.rs index fbe9ed4..fbe9ed4 100644 --- a/src/message.rs +++ b/2.27.1/src/message.rs diff --git a/src/oneof.rs b/2.27.1/src/oneof.rs index 44201ea..44201ea 100644 --- a/src/oneof.rs +++ b/2.27.1/src/oneof.rs diff --git a/src/protobuf_name.rs b/2.27.1/src/protobuf_name.rs index c40a69c..c40a69c 100644 --- a/src/protobuf_name.rs +++ b/2.27.1/src/protobuf_name.rs diff --git a/src/rust.rs b/2.27.1/src/rust.rs index 263cba0..263cba0 100644 --- a/src/rust.rs +++ b/2.27.1/src/rust.rs diff --git a/src/rust_name.rs b/2.27.1/src/rust_name.rs index 234925b..234925b 100644 --- a/src/rust_name.rs +++ b/2.27.1/src/rust_name.rs diff --git a/src/rust_types_values.rs b/2.27.1/src/rust_types_values.rs index e9b017d..e9b017d 100644 --- a/src/rust_types_values.rs +++ b/2.27.1/src/rust_types_values.rs diff --git a/src/scope.rs b/2.27.1/src/scope.rs index 5f92d08..5f92d08 100644 --- a/src/scope.rs +++ b/2.27.1/src/scope.rs diff --git a/src/serde.rs b/2.27.1/src/serde.rs index f799611..f799611 100644 --- a/src/serde.rs +++ b/2.27.1/src/serde.rs diff --git a/src/strx.rs b/2.27.1/src/strx.rs index d1b26fa..d1b26fa 100644 --- a/src/strx.rs +++ b/2.27.1/src/strx.rs diff --git a/src/syntax.rs b/2.27.1/src/syntax.rs index d075063..d075063 100644 --- a/src/syntax.rs +++ b/2.27.1/src/syntax.rs diff --git a/src/well_known_types.rs b/2.27.1/src/well_known_types.rs index 6264947..6264947 100644 --- a/src/well_known_types.rs +++ b/2.27.1/src/well_known_types.rs @@ -24,28 +24,42 @@ rust_library_host { name: "libprotobuf_codegen", crate_name: "protobuf_codegen", cargo_env_compat: true, - cargo_pkg_version: "2.27.1", + cargo_pkg_version: "3.2.0", srcs: ["src/lib.rs"], - edition: "2015", + edition: "2021", rustlibs: [ + "libanyhow", + "libonce_cell", "libprotobuf", + "libprotobuf_parse", + "libregex", + "libtempfile", + "libthiserror", ], + product_available: true, + vendor_available: true, } rust_test_host { name: "protobuf-codegen_test_src_lib", crate_name: "protobuf_codegen", cargo_env_compat: true, - cargo_pkg_version: "2.27.1", + cargo_pkg_version: "3.2.0", srcs: ["src/lib.rs"], test_suites: ["general-tests"], auto_gen_config: true, test_options: { unit_test: true, }, - edition: "2015", + edition: "2021", rustlibs: [ + "libanyhow", + "libonce_cell", "libprotobuf", + "libprotobuf_parse", + "libregex", + "libtempfile", + "libthiserror", ], } @@ -53,11 +67,19 @@ rust_binary_host { name: "protoc-gen-rust", crate_name: "protoc_gen_rust", cargo_env_compat: true, - cargo_pkg_version: "2.27.1", + cargo_pkg_version: "3.2.0", srcs: ["src/bin/protoc-gen-rust.rs"], - edition: "2015", + edition: "2021", rustlibs: [ + "libanyhow", + "libonce_cell", "libprotobuf", "libprotobuf_codegen", + "libprotobuf_parse", + "libregex", + "libtempfile", + "libthiserror", ], + product_available: true, + vendor_available: true, } @@ -3,14 +3,287 @@ version = 3 [[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "libc" +version = "0.2.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "proc-macro2" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58" +dependencies = [ + "unicode-ident", +] + +[[package]] name = "protobuf" -version = "2.27.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" +checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror", +] [[package]] name = "protobuf-codegen" -version = "2.27.1" +version = "3.2.0" +dependencies = [ + "anyhow", + "once_cell", + "protobuf", + "protobuf-parse", + "regex", + "tempfile", + "thiserror", +] + +[[package]] +name = "protobuf-parse" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d39b14605eaa1f6a340aec7f320b34064feb26c93aec35d6a9a2272a8ddfa49" dependencies = [ + "anyhow", + "indexmap", + "log", "protobuf", + "protobuf-support", + "tempfile", + "thiserror", + "which", +] + +[[package]] +name = "protobuf-support" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" +dependencies = [ + "thiserror", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122" +dependencies = [ + "thiserror-impl", ] + +[[package]] +name = "thiserror-impl" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" + +[[package]] +name = "which" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" @@ -10,17 +10,17 @@ # See Cargo.toml.orig for the original contents. [package] +edition = "2021" name = "protobuf-codegen" -version = "2.27.1" +version = "3.2.0" authors = ["Stepan Koltsov <stepan.koltsov@gmail.com>"] description = """ Code generator for rust-protobuf. -Includes a library and `protoc-gen-rust` binary. - -See `protoc-rust` and `protobuf-codegen-pure` crates. +Includes a library to invoke programmatically (e. g. from `build.rs`) and `protoc-gen-rust` binary. """ homepage = "https://github.com/stepancheg/rust-protobuf/" +readme = "README.md" license = "MIT" repository = "https://github.com/stepancheg/rust-protobuf/" @@ -35,10 +35,23 @@ name = "protoc-gen-rust" path = "src/bin/protoc-gen-rust.rs" test = false -[[bin]] -name = "protobuf-bin-gen-rust-do-not-use" -path = "src/bin/protobuf-bin-gen-rust-do-not-use.rs" -test = false +[dependencies.anyhow] +version = "1.0.53" + +[dependencies.once_cell] +version = "1.10.0" [dependencies.protobuf] -version = "=2.27.1" +version = "=3.2.0" + +[dependencies.protobuf-parse] +version = "=3.2.0" + +[dependencies.regex] +version = "1.5.5" + +[dependencies.tempfile] +version = "3" + +[dependencies.thiserror] +version = "1.0.30" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 3400e66..4cfd140 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,23 +1,29 @@ [package] name = "protobuf-codegen" -version = "2.27.1" +version = "3.2.0" authors = ["Stepan Koltsov <stepan.koltsov@gmail.com>"] +edition = "2021" license = "MIT" homepage = "https://github.com/stepancheg/rust-protobuf/" repository = "https://github.com/stepancheg/rust-protobuf/" description = """ Code generator for rust-protobuf. -Includes a library and `protoc-gen-rust` binary. - -See `protoc-rust` and `protobuf-codegen-pure` crates. +Includes a library to invoke programmatically (e. g. from `build.rs`) and `protoc-gen-rust` binary. """ [lib] bench = false [dependencies] -protobuf = { path = "../protobuf", version = "=2.27.1" } +thiserror = "1.0.30" +anyhow = "1.0.53" +regex = "1.5.5" +once_cell = "1.10.0" +tempfile = "3" + +protobuf = { path = "../protobuf", version = "=3.2.0" } +protobuf-parse = { path = "../protobuf-parse", version = "=3.2.0" } [[bin]] @@ -25,11 +31,5 @@ name = "protoc-gen-rust" path = "src/bin/protoc-gen-rust.rs" test = false -[[bin]] - -name = "protobuf-bin-gen-rust-do-not-use" -path = "src/bin/protobuf-bin-gen-rust-do-not-use.rs" -test = false - [package.metadata.docs.rs] all-features = true @@ -1,3 +1,7 @@ +# This project was upgraded with external_updater. +# Usage: tools/external_updater/updater.sh update rust/crates/protobuf-codegen +# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md + name: "protobuf-codegen" description: "Code generator for rust-protobuf. Includes a library and `protoc-gen-rust` binary. See `protoc-rust` and `protobuf-codegen-pure` crates." third_party { @@ -7,13 +11,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/protobuf-codegen/protobuf-codegen-2.27.1.crate" + value: "https://static.crates.io/crates/protobuf-codegen/protobuf-codegen-3.2.0.crate" } - version: "2.27.1" + version: "3.2.0" license_type: NOTICE last_upgrade_date { - year: 2022 - month: 3 - day: 1 + year: 2023 + month: 4 + day: 3 } } @@ -1,37 +1,65 @@ <!-- cargo-sync-readme start --> -# Protobuf code generator +# Protobuf code generator for `protobuf` crate -This crate contains protobuf code generator implementation -and a `protoc-gen-rust` `protoc` plugin. +This crate is useful mostly from `build.rs` scripts to generate `.rs` files during the build. -This crate: -* provides `protoc-gen-rust` plugin for `protoc` command -* implement protobuf codegen +# How to generate code -This crate is not meant to be used directly, in fact, it does not provide any public API -(except for `protoc-gen-rust` binary). +There are three main ways to generate `.rs` files from `.proto` files: +* using `protoc` command line tool and `protoc-gen-rust` plugin +* using this crate `Codegen` with pure rust parser +* using this crate `Codegen` with `protoc` parser -Code can be generated with either: -* `protoc-gen-rust` plugin for `protoc` or -* [`protoc-rust`](https://docs.rs/protoc) crate - (code generator which depends on `protoc` binary for parsing of `.proto` files) -* [`protobuf-codegen-pure`](https://docs.rs/protobuf-codegen-pure) crate, - similar API to `protoc-rust`, but uses pure rust parser of `.proto` files. +Which one should you use depends on your needs. -# `protoc-gen-rust` plugin for `protoc` +If you are using non-cargo build system (like Bazel), you might prefer +using `protoc-gen-rust` plugin for `protoc`. -When non-cargo build system is used, consider using standard protobuf code generation pattern: -`protoc` command does all the work of handling paths and parsing `.proto` files. -When `protoc` is invoked with `--rust_out=` option, it invokes `protoc-gen-rust` plugin. -provided by this crate. +If you build with `cargo`, you probably want to use `Codegen` from this crate. -When building with cargo, consider using `protoc-rust` or `protobuf-codegen-pure` crates. +# Protoc parser vs pure rust parser -## How to use `protoc-gen-rust` if you have to +There are two protobuf parsers which can be plugged into this crate: +* `protoc`-based parser (`protoc` is a command like utility from Google protobuf) +* pure rust parser (`protobuf-parse` crate) + +`protoc`-based parser is expected to parse `.proto` files very correctly: +all Google's protobuf implementations rely on it. + +While there are no known bugs in `protobuf-parse`, it is not tested very well. +Also `protobuf-parse` does not implement certain rarely used features of `.proto` parser, +mostly complex message options specified in `.proto` files. +I never saw anyone using them, but you have been warned. + +Note `protoc` command can be obtained from +[`protoc-bin-vendored`](https://docs.rs/protoc-bin-vendored) crate. + +# Example + +```rust +// Use this in build.rs +protobuf_codegen::Codegen::new() + // Use `protoc` parser, optional. + .protoc() + // Use `protoc-bin-vendored` bundled protoc command, optional. + .protoc_path(&protoc_bin_vendored::protoc_bin_path().unwrap()) + // All inputs and imports from the inputs must reside in `includes` directories. + .includes(&["src/protos"]) + // Inputs must reside in some of include paths. + .input("src/protos/apple.proto") + .input("src/protos/banana.proto") + // Specify output directory relative to Cargo output directory. + .cargo_out_dir("protos") + .run_from_script(); +``` + +## How to use `protoc-gen-rust` + +If you have to. (Note `protoc` can be invoked programmatically with -[protoc crate](https://docs.rs/protoc)) +[protoc crate](https://docs.rs/protoc/%3E=3.0.0-alpha)) 0) Install protobuf for `protoc` binary. @@ -48,11 +76,11 @@ apt-get install protobuf-compiler ``` Protobuf is needed only for code generation, `rust-protobuf` runtime -does not use `protobuf` library. +does not use C++ protobuf library. 1) Install `protoc-gen-rust` program (which is `protoc` plugin) -It can be installed either from source or with `cargo install protobuf` command. +It can be installed either from source or with `cargo install protobuf-codegen` command. 2) Add `protoc-gen-rust` to $PATH @@ -70,11 +98,32 @@ protoc --rust_out . foo.proto This will generate .rs files in current directory. -# Version 2 +# Customize generate code + +Sometimes generated code need to be adjusted, e. g. to have custom derives. + +rust-protobuf provides two options to do that: +* generated `.rs` files contain `@@protoc_insertion_point(...)` markers + (similar markers inserts Google's protobuf generator for C++ or Java). + Simple script `sed` one-liners can be used to replace these markers with custom annotations. +* `Codegen::customize_callback` can be used to patch generated code + when invoked from `build.rs` script. + +# Serde + +rust-protobuf since version 3 no longer directly supports serde. + +Rust-protobuf 3 fully supports: +* runtime reflection +* JSON parsing and printing via + [`protobuf-json-mapping`](https://docs.rs/protobuf-json-mapping) + +Which covers the most of serde use cases. -This is documentation for version 2 of the crate. +If you still need serde, generic customization callback (see above) can be used +to insert `#[serde(...)]` annotations. -[Version 3 of the crate](https://docs.rs/protobuf-codegen/%3E=3.0.0-alpha) -(currently in development) encapsulates both `protoc` and pure codegens in this crate. +[Example project](https://github.com/stepancheg/rust-protobuf/tree/master/protobuf-examples/customize-serde) +in the rust-protobuf repository demonstrates how to do it. <!-- cargo-sync-readme end --> diff --git a/src/bin/protoc-gen-rust.rs b/src/bin/protoc-gen-rust.rs index 97ac7d2..8c34e49 100644 --- a/src/bin/protoc-gen-rust.rs +++ b/src/bin/protoc-gen-rust.rs @@ -1,5 +1,3 @@ -extern crate protobuf_codegen; - fn main() { - protobuf_codegen::protoc_gen_rust_main(); + protobuf_codegen::protoc_gen_rust::protoc_gen_rust_main(); } diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs new file mode 100644 index 0000000..3b8f629 --- /dev/null +++ b/src/codegen/mod.rs @@ -0,0 +1,272 @@ +use std::env; +use std::ffi::OsString; +use std::fs; +use std::path::Component; +use std::path::Path; +use std::path::PathBuf; +use std::process; + +use anyhow::Context; +use protobuf_parse::Parser; + +use crate::customize::CustomizeCallback; +use crate::customize::CustomizeCallbackHolder; +use crate::gen_and_write::gen_and_write; +use crate::Customize; + +#[derive(Debug)] +enum WhichParser { + Pure, + Protoc, +} + +impl Default for WhichParser { + fn default() -> WhichParser { + WhichParser::Pure + } +} + +#[derive(Debug, thiserror::Error)] +enum CodegenError { + #[error("out_dir is not specified")] + OutDirNotSpecified, +} + +/// Entry point for `.proto` to `.rs` code generation. +/// +/// This is similar to `protoc --rust_out...`. +#[derive(Debug, Default)] +pub struct Codegen { + /// What parser to use to parse `.proto` files. + which_parser: Option<WhichParser>, + /// Create out directory. + create_out_dir: bool, + /// --lang_out= param + out_dir: Option<PathBuf>, + /// -I args + includes: Vec<PathBuf>, + /// List of .proto files to compile + inputs: Vec<PathBuf>, + /// Customize code generation + customize: Customize, + /// Customize code generation + customize_callback: CustomizeCallbackHolder, + /// Protoc command path + protoc: Option<PathBuf>, + /// Extra `protoc` args + protoc_extra_args: Vec<OsString>, + /// Capture stderr when running `protoc`. + capture_stderr: bool, +} + +impl Codegen { + /// Create new codegen object. + /// + /// Uses `protoc` from `$PATH` by default. + /// + /// Can be switched to pure rust parser using [`pure`](Self::pure) function. + pub fn new() -> Self { + Self::default() + } + + /// Switch to pure Rust parser of `.proto` files. + pub fn pure(&mut self) -> &mut Self { + self.which_parser = Some(WhichParser::Pure); + self + } + + /// Switch to `protoc` parser of `.proto` files. + pub fn protoc(&mut self) -> &mut Self { + self.which_parser = Some(WhichParser::Protoc); + self + } + + /// Output directory for generated code. + /// + /// When invoking from `build.rs`, consider using + /// [`cargo_out_dir`](Self::cargo_out_dir) instead. + pub fn out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self { + self.out_dir = Some(out_dir.as_ref().to_owned()); + self + } + + /// Set output directory relative to Cargo output dir. + /// + /// With this option, output directory is erased and recreated during invocation. + pub fn cargo_out_dir(&mut self, rel: &str) -> &mut Self { + let rel = Path::new(rel); + let mut not_empty = false; + for comp in rel.components() { + match comp { + Component::ParentDir => { + panic!("parent path in components of rel path: `{}`", rel.display()); + } + Component::CurDir => { + continue; + } + Component::Normal(..) => {} + Component::RootDir | Component::Prefix(..) => { + panic!("root dir in components of rel path: `{}`", rel.display()); + } + } + not_empty = true; + } + + if !not_empty { + panic!("empty rel path: `{}`", rel.display()); + } + + let cargo_out_dir = env::var("OUT_DIR").expect("OUT_DIR env var not set"); + let mut path = PathBuf::from(cargo_out_dir); + path.push(rel); + self.create_out_dir = true; + self.out_dir(path) + } + + /// Add an include directory. + pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self { + self.includes.push(include.as_ref().to_owned()); + self + } + + /// Add include directories. + pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self { + for include in includes { + self.include(include); + } + self + } + + /// Append a `.proto` file path to compile + pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self { + self.inputs.push(input.as_ref().to_owned()); + self + } + + /// Append multiple `.proto` file paths to compile + pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self { + for input in inputs { + self.input(input); + } + self + } + + /// Specify `protoc` command path to be used when invoking code generation. + /// + /// # Examples + /// + /// ```no_run + /// # mod protoc_bin_vendored { + /// # pub fn protoc_bin_path() -> Result<std::path::PathBuf, std::io::Error> { + /// # unimplemented!() + /// # } + /// # } + /// + /// use protobuf_codegen::Codegen; + /// + /// Codegen::new() + /// .protoc() + /// .protoc_path(&protoc_bin_vendored::protoc_bin_path().unwrap()) + /// // ... + /// .run() + /// .unwrap(); + /// ``` + /// + /// This option is ignored when pure Rust parser is used. + pub fn protoc_path(&mut self, protoc: &Path) -> &mut Self { + self.protoc = Some(protoc.to_owned()); + self + } + + /// Capture stderr to error when running `protoc`. + pub fn capture_stderr(&mut self) -> &mut Self { + self.capture_stderr = true; + self + } + + /// Extra command line flags for `protoc` invocation. + /// + /// For example, `--experimental_allow_proto3_optional` option. + /// + /// This option is ignored when pure Rust parser is used. + pub fn protoc_extra_arg(&mut self, arg: impl Into<OsString>) -> &mut Self { + self.protoc_extra_args.push(arg.into()); + self + } + + /// Set options to customize code generation + pub fn customize(&mut self, customize: Customize) -> &mut Self { + self.customize.update_with(&customize); + self + } + + /// Callback for dynamic per-element customization. + pub fn customize_callback(&mut self, callback: impl CustomizeCallback) -> &mut Self { + self.customize_callback = CustomizeCallbackHolder::new(callback); + self + } + + /// Invoke the code generation. + /// + /// This is roughly equivalent to `protoc --rust_out=...` but + /// without requiring `protoc-gen-rust` command in `$PATH`. + /// + /// This function uses pure Rust parser or `protoc` parser depending on + /// how this object was configured. + pub fn run(&self) -> anyhow::Result<()> { + let out_dir = match &self.out_dir { + Some(out_dir) => out_dir, + None => return Err(CodegenError::OutDirNotSpecified.into()), + }; + + if self.create_out_dir { + if out_dir.exists() { + fs::remove_dir_all(&out_dir)?; + } + fs::create_dir(&out_dir)?; + } + + let mut parser = Parser::new(); + parser.protoc(); + if let Some(protoc) = &self.protoc { + parser.protoc_path(protoc); + } + match &self.which_parser { + Some(WhichParser::Protoc) => { + parser.protoc(); + } + Some(WhichParser::Pure) => { + parser.pure(); + } + None => {} + } + + parser.inputs(&self.inputs); + parser.includes(&self.includes); + + if self.capture_stderr { + parser.capture_stderr(); + } + + let parsed_and_typechecked = parser + .parse_and_typecheck() + .context("parse and typecheck")?; + + gen_and_write( + &parsed_and_typechecked.file_descriptors, + &parsed_and_typechecked.parser, + &parsed_and_typechecked.relative_paths, + &out_dir, + &self.customize, + &*self.customize_callback, + ) + } + + /// Similar to `run`, but prints the message to stderr and exits the process on error. + pub fn run_from_script(&self) { + if let Err(e) = self.run() { + eprintln!("codegen failed: {:?}", e); + process::exit(1); + } + } +} diff --git a/src/compiler_plugin.rs b/src/compiler_plugin.rs new file mode 100644 index 0000000..b7ff6ea --- /dev/null +++ b/src/compiler_plugin.rs @@ -0,0 +1,48 @@ +use std::io::stdin; +use std::io::stdout; +use std::str; + +use protobuf::descriptor::FileDescriptorProto; +use protobuf::plugin::*; +use protobuf::Message; +use protobuf_parse::ProtoPathBuf; + +pub struct GenRequest<'a> { + pub file_descriptors: &'a [FileDescriptorProto], + pub files_to_generate: &'a [ProtoPathBuf], + pub parameter: &'a str, +} + +pub struct GenResult { + pub name: String, + pub content: Vec<u8>, +} + +pub fn plugin_main<F>(gen: F) -> anyhow::Result<()> +where + F: Fn(&GenRequest) -> anyhow::Result<Vec<GenResult>>, +{ + let req = CodeGeneratorRequest::parse_from_reader(&mut stdin()).unwrap(); + let result = gen(&GenRequest { + file_descriptors: &req.proto_file, + files_to_generate: &req + .file_to_generate + .iter() + .map(|n| ProtoPathBuf::new(n.to_owned())) + .collect::<anyhow::Result<Vec<_>>>()?, + parameter: req.parameter(), + })?; + let mut resp = CodeGeneratorResponse::new(); + resp.set_supported_features(code_generator_response::Feature::FEATURE_PROTO3_OPTIONAL as u64); + resp.file = result + .iter() + .map(|file| { + let mut r = code_generator_response::File::new(); + r.set_name(file.name.to_string()); + r.set_content(str::from_utf8(file.content.as_ref()).unwrap().to_string()); + r + }) + .collect(); + resp.write_to_writer(&mut stdout()).unwrap(); + Ok(()) +} diff --git a/src/customize/ctx.rs b/src/customize/ctx.rs new file mode 100644 index 0000000..185fc31 --- /dev/null +++ b/src/customize/ctx.rs @@ -0,0 +1,92 @@ +use std::fmt; + +use protobuf::reflect::EnumDescriptor; +use protobuf::reflect::FieldDescriptor; +use protobuf::reflect::FileDescriptor; +use protobuf::reflect::MessageDescriptor; +use protobuf::reflect::OneofDescriptor; + +use crate::customize::CustomizeCallback; +use crate::Customize; + +#[derive(Clone)] +pub(crate) struct CustomizeElemCtx<'a> { + pub(crate) for_elem: Customize, + pub(crate) for_children: Customize, + pub(crate) callback: &'a dyn CustomizeCallback, +} + +impl<'a> fmt::Debug for CustomizeElemCtx<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("CustomizeElemCtx") + .field("for_elem", &self.for_elem) + .field("for_children", &self.for_children) + .finish_non_exhaustive() + } +} + +impl<'a> CustomizeElemCtx<'a> { + pub(crate) fn child( + &self, + elem_from_rustproto: &Customize, + elem_descriptor: &impl DescriptorForCustomize, + ) -> CustomizeElemCtx<'a> { + let mut for_elem = self.for_children.clone(); + for_elem.update_with(elem_from_rustproto); + + let for_children = for_elem.clone(); + + for_elem.update_with(&elem_descriptor.customize(self.callback)); + + CustomizeElemCtx { + for_elem, + for_children, + callback: self.callback, + } + } +} + +pub(crate) trait DescriptorForCustomize { + fn customize(&self, callback: &dyn CustomizeCallback) -> Customize; +} + +impl DescriptorForCustomize for MessageDescriptor { + fn customize(&self, callback: &dyn CustomizeCallback) -> Customize { + callback.message(self) + } +} + +impl DescriptorForCustomize for FieldDescriptor { + fn customize(&self, callback: &dyn CustomizeCallback) -> Customize { + callback.field(self) + } +} + +impl DescriptorForCustomize for EnumDescriptor { + fn customize(&self, callback: &dyn CustomizeCallback) -> Customize { + callback.enumeration(self) + } +} + +impl DescriptorForCustomize for OneofDescriptor { + fn customize(&self, callback: &dyn CustomizeCallback) -> Customize { + callback.oneof(self) + } +} + +impl DescriptorForCustomize for FileDescriptor { + fn customize(&self, callback: &dyn CustomizeCallback) -> Customize { + callback.file(self) + } +} + +pub(crate) struct SpecialFieldPseudoDescriptor<'a> { + pub(crate) message: &'a MessageDescriptor, + pub(crate) field: &'a str, +} + +impl<'a> DescriptorForCustomize for SpecialFieldPseudoDescriptor<'a> { + fn customize(&self, callback: &dyn CustomizeCallback) -> Customize { + callback.special_field(self.message, self.field) + } +} diff --git a/src/customize/mod.rs b/src/customize/mod.rs new file mode 100644 index 0000000..826bd3f --- /dev/null +++ b/src/customize/mod.rs @@ -0,0 +1,251 @@ +pub(crate) mod ctx; +pub(crate) mod rustproto_proto; + +use std::fmt; +use std::ops::Deref; +use std::rc::Rc; + +use protobuf::reflect::EnumDescriptor; +use protobuf::reflect::FieldDescriptor; +use protobuf::reflect::FileDescriptor; +use protobuf::reflect::MessageDescriptor; +use protobuf::reflect::OneofDescriptor; + +/// Dynamic callback to customize code generation. +pub trait CustomizeCallback: 'static { + fn file(&self, file: &FileDescriptor) -> Customize { + let _ = file; + Customize::default() + } + + fn message(&self, message: &MessageDescriptor) -> Customize { + let _ = message; + Customize::default() + } + + fn field(&self, field: &FieldDescriptor) -> Customize { + let _ = field; + Customize::default() + } + + fn special_field(&self, message: &MessageDescriptor, field: &str) -> Customize { + let _ = (message, field); + Customize::default() + } + + fn enumeration(&self, enum_type: &EnumDescriptor) -> Customize { + let _ = enum_type; + Customize::default() + } + + fn oneof(&self, oneof: &OneofDescriptor) -> Customize { + let _ = oneof; + Customize::default() + } +} + +pub(crate) struct CustomizeCallbackDefault; +impl CustomizeCallback for CustomizeCallbackDefault {} + +#[derive(Clone)] +pub(crate) struct CustomizeCallbackHolder(pub(crate) Rc<dyn CustomizeCallback>); + +impl CustomizeCallbackHolder { + pub(crate) fn new(callback: impl CustomizeCallback) -> CustomizeCallbackHolder { + CustomizeCallbackHolder(Rc::new(callback)) + } +} + +impl Deref for CustomizeCallbackHolder { + type Target = dyn CustomizeCallback; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl Default for CustomizeCallbackHolder { + fn default() -> Self { + CustomizeCallbackHolder(Rc::new(CustomizeCallbackDefault)) + } +} + +impl PartialEq for CustomizeCallbackHolder { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} + +impl fmt::Debug for CustomizeCallbackHolder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("CustomizeCallbackWrapper") + .finish_non_exhaustive() + } +} + +/// Specifies style of generated code. +/// Generated files can be customized using this proto +/// or using `rustproto.proto` options. +#[derive(Default, Debug, Clone, PartialEq)] +pub struct Customize { + /// Code to insert before the element in the generated file. + pub(crate) before: Option<String>, + /// When false, `get_`, `set_`, `mut_` etc. accessors are not generated + pub(crate) generate_accessors: Option<bool>, + /// When false, `get_` is not generated even if `syntax = "proto2"` + pub(crate) generate_getter: Option<bool>, + /// Use `bytes::Bytes` for `bytes` fields + pub(crate) tokio_bytes: Option<bool>, + /// Use `bytes::Bytes` for `string` fields + pub(crate) tokio_bytes_for_string: Option<bool>, + /// Enable lite runtime. + pub(crate) lite_runtime: Option<bool>, + /// Generate `mod.rs` in the output directory. + /// + /// This option allows inclusion of generated files from cargo output directory. + /// + /// This option will likely be on by default in rust-protobuf version 3. + pub(crate) gen_mod_rs: Option<bool>, + /// Used internally to generate protos bundled in protobuf crate + /// like `descriptor.proto` + pub(crate) inside_protobuf: Option<bool>, +} + +#[derive(Debug, thiserror::Error)] +pub(crate) enum CustomizeParseParameterError { + #[error("Cannot parse bool option value: {:?}", .0)] + CannotParseBool(String), + #[error("Unknown option name: {:?}", .0)] + UnknownOptionName(String), +} + +impl Customize { + /// Insert code before the element in the generated file + /// (e. g. serde annotations, see + /// [example here](https://github.com/stepancheg/rust-protobuf/tree/master/protobuf-examples/customize-serde)). + pub fn before(mut self, before: &str) -> Self { + self.before = Some(before.to_owned()); + self + } + + pub fn generate_accessors(mut self, generate_accessors: bool) -> Self { + self.generate_accessors = Some(generate_accessors); + self + } + + pub fn generate_getter(mut self, generate_getter: bool) -> Self { + self.generate_getter = Some(generate_getter); + self + } + + pub fn tokio_bytes(mut self, tokio_bytes: bool) -> Self { + self.tokio_bytes = Some(tokio_bytes); + self + } + + pub fn tokio_bytes_for_string(mut self, tokio_bytes_for_string: bool) -> Self { + self.tokio_bytes_for_string = Some(tokio_bytes_for_string); + self + } + + /// Generate code for "lite runtime". Generated code contains no code for reflection. + /// So the generated code (and more importantly, generated binary size) is smaller, + /// but reflection, text format, JSON serialization won't work. + /// + /// Note when using `protoc` plugin `protoc-gen-rust`, the option name is just `lite`. + pub fn lite_runtime(mut self, lite_runtime: bool) -> Self { + self.lite_runtime = Some(lite_runtime); + self + } + + /// Generate `mod.rs` with all the generated modules. + /// This option is on by default in rust-protobuf version 3. + pub fn gen_mod_rs(mut self, gen_mod_rs: bool) -> Self { + self.gen_mod_rs = Some(gen_mod_rs); + self + } + + /// Generate code bundled in protobuf crate. Regular users don't need this option. + pub fn inside_protobuf(mut self, inside_protobuf: bool) -> Self { + self.inside_protobuf = Some(inside_protobuf); + self + } + + /// Update fields of self with fields defined in other customize + pub fn update_with(&mut self, that: &Customize) { + if let Some(v) = &that.before { + self.before = Some(v.clone()); + } + if let Some(v) = that.generate_accessors { + self.generate_accessors = Some(v); + } + if let Some(v) = that.generate_getter { + self.generate_getter = Some(v); + } + if let Some(v) = that.tokio_bytes { + self.tokio_bytes = Some(v); + } + if let Some(v) = that.tokio_bytes_for_string { + self.tokio_bytes_for_string = Some(v); + } + if let Some(v) = that.lite_runtime { + self.lite_runtime = Some(v); + } + if let Some(v) = that.gen_mod_rs { + self.gen_mod_rs = Some(v); + } + if let Some(v) = that.inside_protobuf { + self.inside_protobuf = Some(v); + } + } + + /// Update unset fields of self with fields from other customize + pub fn set_defaults_from(&mut self, other: &Customize) { + let mut tmp = other.clone(); + tmp.update_with(self); + *self = tmp; + } + + /// Parse customize options from a string passed via protoc flag. + pub fn parse_from_parameter(parameter: &str) -> anyhow::Result<Customize> { + fn parse_bool(v: &str) -> anyhow::Result<bool> { + v.parse() + .map_err(|_| CustomizeParseParameterError::CannotParseBool(v.to_owned()).into()) + } + + let mut r = Customize::default(); + for nv in parameter.split_whitespace() { + let (n, v) = match nv.find('=') { + Some(eq) => { + let n = &nv[..eq]; + let v = &nv[eq + 1..]; + (n, v) + } + None => (nv, "true"), + }; + + if n == "generate_accessors" { + r.generate_accessors = Some(parse_bool(v)?); + } else if n == "generate_getter" { + r.generate_getter = Some(parse_bool(v)?); + } else if n == "tokio_bytes" { + r.tokio_bytes = Some(parse_bool(v)?); + } else if n == "tokio_bytes_for_string" { + r.tokio_bytes_for_string = Some(parse_bool(v)?); + } else if n == "lite_runtime" { + r.lite_runtime = Some(parse_bool(v)?); + } else if n == "gen_mod_rs" { + r.gen_mod_rs = Some(parse_bool(v)?); + } else if n == "inside_protobuf" { + r.inside_protobuf = Some(parse_bool(v)?); + } else if n == "lite" { + // Support Java and C++ protoc plugin syntax: + // https://github.com/protocolbuffers/protobuf/issues/6489 + r.lite_runtime = Some(parse_bool(v)?); + } else { + return Err(CustomizeParseParameterError::UnknownOptionName(n.to_owned()).into()); + } + } + Ok(r) + } +} diff --git a/src/customize/rustproto_proto.rs b/src/customize/rustproto_proto.rs new file mode 100644 index 0000000..3d9a77b --- /dev/null +++ b/src/customize/rustproto_proto.rs @@ -0,0 +1,74 @@ +use protobuf::descriptor::EnumOptions; +use protobuf::descriptor::FieldOptions; +use protobuf::descriptor::FileOptions; +use protobuf::descriptor::MessageOptions; +use protobuf::rustproto; + +use crate::Customize; + +pub(crate) fn customize_from_rustproto_for_message(source: &MessageOptions) -> Customize { + let before = None; + let generate_accessors = rustproto::exts::generate_accessors.get(source); + let generate_getter = rustproto::exts::generate_getter.get(source); + let tokio_bytes = rustproto::exts::tokio_bytes.get(source); + let tokio_bytes_for_string = rustproto::exts::tokio_bytes_for_string.get(source); + let lite_runtime = None; + let gen_mod_rs = None; + let inside_protobuf = None; + Customize { + before, + generate_accessors, + generate_getter, + tokio_bytes, + tokio_bytes_for_string, + lite_runtime, + gen_mod_rs, + inside_protobuf, + } +} + +pub(crate) fn customize_from_rustproto_for_enum(_source: &EnumOptions) -> Customize { + Customize::default() +} + +pub(crate) fn customize_from_rustproto_for_field(source: &FieldOptions) -> Customize { + let before = None; + let generate_accessors = rustproto::exts::generate_accessors_field.get(source); + let generate_getter = rustproto::exts::generate_getter_field.get(source); + let tokio_bytes = rustproto::exts::tokio_bytes_field.get(source); + let tokio_bytes_for_string = rustproto::exts::tokio_bytes_for_string_field.get(source); + let lite_runtime = None; + let gen_mod_rs = None; + let inside_protobuf = None; + Customize { + before, + generate_accessors, + generate_getter, + tokio_bytes, + tokio_bytes_for_string, + lite_runtime, + gen_mod_rs, + inside_protobuf, + } +} + +pub(crate) fn customize_from_rustproto_for_file(source: &FileOptions) -> Customize { + let before = None; + let generate_accessors = rustproto::exts::generate_accessors_all.get(source); + let generate_getter = rustproto::exts::generate_getter_all.get(source); + let tokio_bytes = rustproto::exts::tokio_bytes_all.get(source); + let tokio_bytes_for_string = rustproto::exts::tokio_bytes_for_string_all.get(source); + let lite_runtime = rustproto::exts::lite_runtime_all.get(source); + let gen_mod_rs = None; + let inside_protobuf = None; + Customize { + before, + generate_accessors, + generate_getter, + tokio_bytes, + tokio_bytes_for_string, + lite_runtime, + inside_protobuf, + gen_mod_rs, + } +} diff --git a/src/gen/all.rs b/src/gen/all.rs new file mode 100644 index 0000000..312b8db --- /dev/null +++ b/src/gen/all.rs @@ -0,0 +1,64 @@ +use std::collections::HashMap; + +use protobuf::descriptor::FileDescriptorProto; +use protobuf::reflect::FileDescriptor; +use protobuf_parse::ProtoPath; +use protobuf_parse::ProtoPathBuf; + +use crate::compiler_plugin; +use crate::customize::ctx::CustomizeElemCtx; +use crate::customize::CustomizeCallback; +use crate::gen::file::gen_file; +use crate::gen::mod_rs::gen_mod_rs; +use crate::gen::scope::RootScope; +use crate::gen::well_known_types::gen_well_known_types_mod; +use crate::Customize; + +pub(crate) fn gen_all( + file_descriptors: &[FileDescriptorProto], + parser: &str, + files_to_generate: &[ProtoPathBuf], + customize: &Customize, + customize_callback: &dyn CustomizeCallback, +) -> anyhow::Result<Vec<compiler_plugin::GenResult>> { + let file_descriptors = FileDescriptor::new_dynamic_fds(file_descriptors.to_vec(), &[])?; + + let root_scope = RootScope { + file_descriptors: &file_descriptors, + }; + + let mut results: Vec<compiler_plugin::GenResult> = Vec::new(); + let files_map: HashMap<&ProtoPath, &FileDescriptor> = file_descriptors + .iter() + .map(|f| Ok((ProtoPath::new(f.proto().name())?, f))) + .collect::<Result<_, anyhow::Error>>()?; + + let mut mods = Vec::new(); + + let customize = CustomizeElemCtx { + for_elem: customize.clone(), + for_children: customize.clone(), + callback: customize_callback, + }; + + for file_name in files_to_generate { + let file = files_map.get(file_name.as_path()).expect(&format!( + "file not found in file descriptors: {:?}, files: {:?}", + file_name, + files_map.keys() + )); + let gen_file_result = gen_file(file, &files_map, &root_scope, &customize, parser)?; + results.push(gen_file_result.compiler_plugin_result); + mods.push(gen_file_result.mod_name); + } + + if customize.for_elem.inside_protobuf.unwrap_or(false) { + results.push(gen_well_known_types_mod()); + } + + if customize.for_elem.gen_mod_rs.unwrap_or(true) { + results.push(gen_mod_rs(&mods)); + } + + Ok(results) +} diff --git a/src/gen/code_writer.rs b/src/gen/code_writer.rs new file mode 100644 index 0000000..28b3817 --- /dev/null +++ b/src/gen/code_writer.rs @@ -0,0 +1,455 @@ +use std::convert::Infallible; + +use crate::gen::rust::rel_path::RustRelativePath; + +/// Field visibility. +pub(crate) enum Visibility { + Public, + Default, + Path(RustRelativePath), +} + +pub(crate) struct CodeWriter<'a> { + writer: &'a mut String, + indent: String, +} + +impl<'a> CodeWriter<'a> { + pub(crate) fn new(writer: &'a mut String) -> CodeWriter<'a> { + CodeWriter { + writer, + indent: "".to_string(), + } + } + + pub(crate) fn with_no_error(f: impl FnOnce(&mut CodeWriter)) -> String { + Self::with_impl::<Infallible, _>(|w| Ok(f(w))).unwrap_or_else(|e| match e {}) + } + + pub(crate) fn with<F>(f: F) -> anyhow::Result<String> + where + F: FnOnce(&mut CodeWriter) -> anyhow::Result<()>, + { + Self::with_impl(f) + } + + fn with_impl<E, F>(f: F) -> Result<String, E> + where + F: FnOnce(&mut CodeWriter) -> Result<(), E>, + { + let mut writer = String::new(); + { + let mut cw = CodeWriter::new(&mut writer); + f(&mut cw)?; + } + Ok(writer) + } + + pub(crate) fn write_line<S: AsRef<str>>(&mut self, line: S) { + if line.as_ref().is_empty() { + self.writer.push_str("\n"); + } else { + let s: String = [self.indent.as_ref(), line.as_ref(), "\n"].concat(); + self.writer.push_str(&s); + } + } + + pub(crate) fn _write_text(&mut self, text: &str) { + for line in text.lines() { + self.write_line(line); + } + } + + pub(crate) fn write_generated_by(&mut self, pkg: &str, version: &str, parser: &str) { + self.write_line(format!( + "// This file is generated by {pkg} {version}. Do not edit", + pkg = pkg, + version = version + )); + self.write_line(format!( + "// .proto file is parsed by {parser}", + parser = parser + )); + self.write_generated_common(); + } + + fn write_generated_common(&mut self) { + // https://secure.phabricator.com/T784 + self.write_line(&format!("// {}generated", "@")); + + self.write_line(""); + self.comment("https://github.com/rust-lang/rust-clippy/issues/702"); + self.write_line("#![allow(unknown_lints)]"); + self.write_line("#![allow(clippy::all)]"); + self.write_line(""); + self.write_line("#![allow(unused_attributes)]"); + self.write_line("#![cfg_attr(rustfmt, rustfmt::skip)]"); + self.write_line(""); + self.write_line("#![allow(box_pointers)]"); + self.write_line("#![allow(dead_code)]"); + self.write_line("#![allow(missing_docs)]"); + self.write_line("#![allow(non_camel_case_types)]"); + self.write_line("#![allow(non_snake_case)]"); + self.write_line("#![allow(non_upper_case_globals)]"); + self.write_line("#![allow(trivial_casts)]"); + self.write_line("#![allow(unused_results)]"); + self.write_line("#![allow(unused_mut)]"); + } + + pub(crate) fn unimplemented(&mut self) { + self.write_line(format!("unimplemented!();")); + } + + pub(crate) fn indented<F>(&mut self, cb: F) + where + F: FnOnce(&mut CodeWriter), + { + cb(&mut CodeWriter { + writer: self.writer, + indent: format!("{} ", self.indent), + }); + } + + #[allow(dead_code)] + pub(crate) fn commented<F>(&mut self, cb: F) + where + F: Fn(&mut CodeWriter), + { + cb(&mut CodeWriter { + writer: self.writer, + indent: format!("// {}", self.indent), + }); + } + + pub(crate) fn pub_const(&mut self, name: &str, field_type: &str, init: &str) { + self.write_line(&format!("pub const {}: {} = {};", name, field_type, init)); + } + + pub(crate) fn lazy_static(&mut self, name: &str, ty: &str, protobuf_crate_path: &str) { + self.write_line(&format!( + "static {}: {}::rt::Lazy<{}> = {}::rt::Lazy::new();", + name, protobuf_crate_path, ty, protobuf_crate_path, + )); + } + + pub(crate) fn lazy_static_decl_get_simple( + &mut self, + name: &str, + ty: &str, + init: &str, + protobuf_crate_path: &str, + ) { + self.lazy_static(name, ty, protobuf_crate_path); + self.write_line(&format!("{}.get({})", name, init)); + } + + pub(crate) fn lazy_static_decl_get( + &mut self, + name: &str, + ty: &str, + protobuf_crate_path: &str, + init: impl FnOnce(&mut CodeWriter), + ) { + self.lazy_static(name, ty, protobuf_crate_path); + self.block(&format!("{}.get(|| {{", name), "})", init); + } + + pub(crate) fn block<F>(&mut self, first_line: &str, last_line: &str, cb: F) + where + F: FnOnce(&mut CodeWriter), + { + self.write_line(first_line); + self.indented(cb); + self.write_line(last_line); + } + + pub(crate) fn expr_block<F>(&mut self, prefix: &str, cb: F) + where + F: FnOnce(&mut CodeWriter), + { + self.block(&format!("{} {{", prefix), "}", cb); + } + + pub(crate) fn stmt_block<S: AsRef<str>, F>(&mut self, prefix: S, cb: F) + where + F: FnOnce(&mut CodeWriter), + { + self.block(&format!("{} {{", prefix.as_ref()), "};", cb); + } + + pub(crate) fn impl_self_block<S: AsRef<str>, F>(&mut self, name: S, cb: F) + where + F: Fn(&mut CodeWriter), + { + self.expr_block(&format!("impl {}", name.as_ref()), cb); + } + + pub(crate) fn impl_for_block<S1: AsRef<str>, S2: AsRef<str>, F>( + &mut self, + tr: S1, + ty: S2, + cb: F, + ) where + F: Fn(&mut CodeWriter), + { + self.impl_args_for_block(&[], tr.as_ref(), ty.as_ref(), cb); + } + + pub(crate) fn impl_args_for_block<F>(&mut self, args: &[&str], tr: &str, ty: &str, cb: F) + where + F: Fn(&mut CodeWriter), + { + let args_str = if args.is_empty() { + "".to_owned() + } else { + format!("<{}>", args.join(", ")) + }; + self.expr_block(&format!("impl{} {} for {}", args_str, tr, ty), cb); + } + + pub(crate) fn pub_struct<S: AsRef<str>, F>(&mut self, name: S, cb: F) + where + F: Fn(&mut CodeWriter), + { + self.expr_block(&format!("pub struct {}", name.as_ref()), cb); + } + + pub(crate) fn pub_enum<F>(&mut self, name: &str, cb: F) + where + F: Fn(&mut CodeWriter), + { + self.expr_block(&format!("pub enum {}", name), cb); + } + + pub(crate) fn field_entry(&mut self, name: &str, value: &str) { + self.write_line(&format!("{}: {},", name, value)); + } + + pub(crate) fn field_decl(&mut self, name: &str, field_type: &str) { + self.write_line(&format!("{}: {},", name, field_type)); + } + + pub(crate) fn pub_field_decl(&mut self, name: &str, field_type: &str) { + self.write_line(&format!("pub {}: {},", name, field_type)); + } + + pub(crate) fn field_decl_vis(&mut self, vis: Visibility, name: &str, field_type: &str) { + match vis { + Visibility::Public => self.pub_field_decl(name, field_type), + Visibility::Default => self.field_decl(name, field_type), + Visibility::Path(..) => unimplemented!(), + } + } + + pub(crate) fn derive(&mut self, derive: &[&str]) { + let v: Vec<String> = derive.iter().map(|&s| s.to_string()).collect(); + self.write_line(&format!("#[derive({})]", v.join(","))); + } + + pub(crate) fn allow(&mut self, what: &[&str]) { + let v: Vec<String> = what.iter().map(|&s| s.to_string()).collect(); + self.write_line(&format!("#[allow({})]", v.join(","))); + } + + pub(crate) fn comment(&mut self, comment: &str) { + if comment.is_empty() { + self.write_line("//"); + } else { + self.write_line(&format!("// {}", comment)); + } + } + + fn documentation(&mut self, comment: &str) { + if comment.is_empty() { + self.write_line("///"); + } else { + self.write_line(&format!("/// {}", comment)); + } + } + + pub(crate) fn mod_doc(&mut self, comment: &str) { + if comment.is_empty() { + self.write_line("//!"); + } else { + self.write_line(&format!("//! {}", comment)); + } + } + + /// Writes the documentation of the given path. + /// + /// Protobuf paths are defined in proto/google/protobuf/descriptor.proto, + /// in the `SourceCodeInfo` message. + /// + /// For example, say we have a file like: + /// + /// ```ignore + /// message Foo { + /// optional string foo = 1; + /// } + /// ``` + /// + /// Let's look at just the field definition. We have the following paths: + /// + /// ```ignore + /// path represents + /// [ 4, 0, 2, 0 ] The whole field definition. + /// [ 4, 0, 2, 0, 4 ] The label (optional). + /// [ 4, 0, 2, 0, 5 ] The type (string). + /// [ 4, 0, 2, 0, 1 ] The name (foo). + /// [ 4, 0, 2, 0, 3 ] The number (1). + /// ``` + /// + /// The `4`s can be obtained using simple introspection: + /// + /// ``` + /// use protobuf::descriptor::FileDescriptorProto; + /// use protobuf::reflect::MessageDescriptor; + /// + /// let id = MessageDescriptor::for_type::<FileDescriptorProto>() + /// .field_by_name("message_type") + /// .expect("`message_type` must exist") + /// .proto() + /// .number(); + /// + /// assert_eq!(id, 4); + /// ``` + /// + /// The first `0` here means this path refers to the first message. + /// + /// The `2` then refers to the `field` field on the `DescriptorProto` message. + /// + /// Then comes another `0` to refer to the first field of the current message. + /// + /// Etc. + + pub(crate) fn all_documentation( + &mut self, + info: Option<&protobuf::descriptor::SourceCodeInfo>, + path: &[i32], + ) { + let doc = info + .map(|v| &v.location) + .and_then(|ls| ls.iter().find(|l| l.path == path)) + .map(|l| l.leading_comments()); + + let lines = doc + .iter() + .map(|doc| doc.lines()) + .flatten() + .collect::<Vec<_>>(); + + // Skip comments with code blocks to avoid rustdoc trying to compile them. + if !lines.iter().any(|line| line.starts_with(" ")) { + for doc in &lines { + self.documentation(doc); + } + } + } + + pub(crate) fn fn_block<F>(&mut self, vis: Visibility, sig: &str, cb: F) + where + F: FnOnce(&mut CodeWriter), + { + match vis { + Visibility::Public => self.expr_block(&format!("pub fn {}", sig), cb), + Visibility::Default => self.expr_block(&format!("fn {}", sig), cb), + Visibility::Path(p) if p.is_empty() => self.expr_block(&format!("fn {}", sig), cb), + Visibility::Path(p) => self.expr_block(&format!("pub(in {}) fn {}", p, sig), cb), + } + } + + pub(crate) fn pub_fn<F>(&mut self, sig: &str, cb: F) + where + F: FnOnce(&mut CodeWriter), + { + self.fn_block(Visibility::Public, sig, cb); + } + + pub(crate) fn def_fn<F>(&mut self, sig: &str, cb: F) + where + F: Fn(&mut CodeWriter), + { + self.fn_block(Visibility::Default, sig, cb); + } + + pub(crate) fn pub_mod<F>(&mut self, name: &str, cb: F) + where + F: Fn(&mut CodeWriter), + { + self.expr_block(&format!("pub mod {}", name), cb) + } + + pub(crate) fn while_block<S: AsRef<str>, F>(&mut self, cond: S, cb: F) + where + F: Fn(&mut CodeWriter), + { + self.expr_block(&format!("while {}", cond.as_ref()), cb); + } + + // if ... { ... } + pub(crate) fn if_stmt<S: AsRef<str>, F>(&mut self, cond: S, cb: F) + where + F: FnOnce(&mut CodeWriter), + { + self.expr_block(&format!("if {}", cond.as_ref()), cb); + } + + // if ... {} else { ... } + pub(crate) fn if_else_stmt<S: AsRef<str>, F>(&mut self, cond: S, cb: F) + where + F: Fn(&mut CodeWriter), + { + self.write_line(&format!("if {} {{", cond.as_ref())); + self.write_line("} else {"); + self.indented(cb); + self.write_line("}"); + } + + // if let ... = ... { ... } + pub(crate) fn if_let_stmt<F>(&mut self, decl: &str, expr: &str, cb: F) + where + F: FnOnce(&mut CodeWriter), + { + self.if_stmt(&format!("let {} = {}", decl, expr), cb); + } + + // if let ... = ... { } else { ... } + pub(crate) fn if_let_else_stmt<F>(&mut self, decl: &str, expr: &str, cb: F) + where + F: Fn(&mut CodeWriter), + { + self.if_else_stmt(&format!("let {} = {}", decl, expr), cb); + } + + pub(crate) fn for_stmt<S1: AsRef<str>, S2: AsRef<str>, F>(&mut self, over: S1, varn: S2, cb: F) + where + F: FnOnce(&mut CodeWriter), + { + self.stmt_block(&format!("for {} in {}", varn.as_ref(), over.as_ref()), cb) + } + + pub(crate) fn match_block<S: AsRef<str>, F>(&mut self, value: S, cb: F) + where + F: FnOnce(&mut CodeWriter), + { + self.stmt_block(&format!("match {}", value.as_ref()), cb); + } + + pub(crate) fn match_expr<S: AsRef<str>, F>(&mut self, value: S, cb: F) + where + F: Fn(&mut CodeWriter), + { + self.expr_block(&format!("match {}", value.as_ref()), cb); + } + + pub(crate) fn case_block<S: AsRef<str>, F>(&mut self, cond: S, cb: F) + where + F: Fn(&mut CodeWriter), + { + self.block(&format!("{} => {{", cond.as_ref()), "},", cb); + } + + pub(crate) fn case_expr<S1: AsRef<str>, S2: AsRef<str>>(&mut self, cond: S1, body: S2) { + self.write_line(&format!("{} => {},", cond.as_ref(), body.as_ref())); + } +} diff --git a/src/gen/descriptor.rs b/src/gen/descriptor.rs new file mode 100644 index 0000000..53ed061 --- /dev/null +++ b/src/gen/descriptor.rs @@ -0,0 +1,68 @@ +use protobuf::reflect::EnumDescriptor; +use protobuf::reflect::MessageDescriptor; + +use crate::gen::code_writer::CodeWriter; +use crate::gen::file_descriptor::file_descriptor_call_expr; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::scope::Scope; +use crate::Customize; + +/// Abstract message or enum descriptor. +pub(crate) trait Descriptor { + const DESCRIPTOR_FN: &'static str; + const TYPE_NAME: &'static str; + const GET_BY_RELATIVE_NAME_NAME: &'static str; + fn name_to_package(&self) -> &str; +} + +impl Descriptor for MessageDescriptor { + const DESCRIPTOR_FN: &'static str = "descriptor"; + const TYPE_NAME: &'static str = "MessageDescriptor"; + const GET_BY_RELATIVE_NAME_NAME: &'static str = "message_by_package_relative_name"; + + fn name_to_package(&self) -> &str { + self.name_to_package() + } +} + +impl Descriptor for EnumDescriptor { + const DESCRIPTOR_FN: &'static str = "enum_descriptor"; + const TYPE_NAME: &'static str = "EnumDescriptor"; + const GET_BY_RELATIVE_NAME_NAME: &'static str = "enum_by_package_relative_name"; + + fn name_to_package(&self) -> &str { + self.name_to_package() + } +} + +pub(crate) fn write_fn_descriptor<D: Descriptor>( + descriptor: &D, + scope: &Scope, + customize: &Customize, + w: &mut CodeWriter, +) { + let sig = format!( + "{}() -> {}::reflect::{}", + D::DESCRIPTOR_FN, + protobuf_crate_path(customize), + D::TYPE_NAME, + ); + w.def_fn(&sig, |w| { + let expr = format!( + "{}.{}(\"{}\").unwrap()", + file_descriptor_call_expr(scope), + D::GET_BY_RELATIVE_NAME_NAME, + descriptor.name_to_package() + ); + w.lazy_static( + "descriptor", + &format!( + "{}::reflect::{}", + protobuf_crate_path(customize), + D::TYPE_NAME, + ), + &protobuf_crate_path(customize).to_string(), + ); + w.write_line(&format!("descriptor.get(|| {}).clone()", expr)); + }); +} diff --git a/src/gen/enums.rs b/src/gen/enums.rs new file mode 100644 index 0000000..69e9e14 --- /dev/null +++ b/src/gen/enums.rs @@ -0,0 +1,413 @@ +use std::collections::HashSet; + +use protobuf::descriptor::*; + +use crate::customize::ctx::CustomizeElemCtx; +use crate::customize::rustproto_proto::customize_from_rustproto_for_enum; +use crate::gen::code_writer::CodeWriter; +use crate::gen::code_writer::Visibility; +use crate::gen::descriptor::write_fn_descriptor; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::protoc_insertion_point::write_protoc_insertion_point_for_enum; +use crate::gen::protoc_insertion_point::write_protoc_insertion_point_for_enum_value; +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::ident_with_path::RustIdentWithPath; +use crate::gen::rust::snippets::EXPR_NONE; +use crate::gen::scope::EnumValueWithContext; +use crate::gen::scope::EnumWithScope; +use crate::gen::scope::RootScope; +use crate::gen::scope::WithScope; + +#[derive(Clone)] +pub(crate) struct EnumValueGen<'a> { + value: EnumValueWithContext<'a>, + enum_rust_name: RustIdentWithPath, +} + +impl<'a> EnumValueGen<'a> { + fn parse( + value: EnumValueWithContext<'a>, + enum_rust_name: &RustIdentWithPath, + ) -> EnumValueGen<'a> { + EnumValueGen { + value: value.clone(), + enum_rust_name: enum_rust_name.clone(), + } + } + + // enum value + fn number(&self) -> i32 { + self.value.proto.proto().number() + } + + // name of enum variant in generated rust code + pub fn rust_name_inner(&self) -> RustIdent { + self.value.rust_name() + } + + pub fn rust_name_outer(&self) -> RustIdentWithPath { + self.enum_rust_name + .to_path() + .with_ident(self.rust_name_inner()) + } +} + +// Codegen for enum definition +pub(crate) struct EnumGen<'a> { + enum_with_scope: &'a EnumWithScope<'a>, + type_name: RustIdentWithPath, + lite_runtime: bool, + customize: CustomizeElemCtx<'a>, + path: &'a [i32], + info: Option<&'a SourceCodeInfo>, +} + +impl<'a> EnumGen<'a> { + pub fn new( + enum_with_scope: &'a EnumWithScope<'a>, + customize: &CustomizeElemCtx<'a>, + _root_scope: &RootScope, + path: &'a [i32], + info: Option<&'a SourceCodeInfo>, + ) -> EnumGen<'a> { + let customize = customize.child( + &customize_from_rustproto_for_enum(enum_with_scope.en.proto().options.get_or_default()), + &enum_with_scope.en, + ); + let lite_runtime = customize.for_elem.lite_runtime.unwrap_or_else(|| { + enum_with_scope + .file_descriptor() + .proto() + .options + .optimize_for() + == file_options::OptimizeMode::LITE_RUNTIME + }); + + EnumGen { + enum_with_scope, + type_name: enum_with_scope.rust_name().to_path(), + lite_runtime, + customize, + path, + info, + } + } + + fn allow_alias(&self) -> bool { + self.enum_with_scope + .en + .proto() + .options + .get_or_default() + .allow_alias() + } + + fn values_all(&self) -> Vec<EnumValueGen> { + let mut r = Vec::new(); + for p in self.enum_with_scope.values() { + r.push(EnumValueGen::parse(p, &self.type_name)); + } + r + } + + fn values_unique(&self) -> Vec<EnumValueGen> { + let mut used = HashSet::new(); + let mut r = Vec::new(); + for p in self.enum_with_scope.values() { + if !used.insert(p.proto.proto().number()) { + continue; + } + r.push(EnumValueGen::parse(p, &self.type_name)); + } + r + } + + pub fn write(&self, w: &mut CodeWriter) { + self.write_enum(w); + if self.allow_alias() { + w.write_line(""); + self.write_impl_eq(w); + w.write_line(""); + self.write_impl_hash(w); + } + w.write_line(""); + self.write_impl_enum(w); + if !self.lite_runtime { + w.write_line(""); + self.write_impl_enum_full(w); + } + w.write_line(""); + self.write_impl_default(w); + w.write_line(""); + self.write_impl_self(w); + } + + fn write_impl_self(&self, w: &mut CodeWriter) { + if !self.lite_runtime { + w.impl_self_block(&format!("{}", self.type_name), |w| { + self.write_generated_enum_descriptor_data(w); + }); + } + } + + fn write_enum(&self, w: &mut CodeWriter) { + w.all_documentation(self.info, self.path); + + let mut derive = Vec::new(); + derive.push("Clone"); + derive.push("Copy"); + if !self.allow_alias() { + derive.push("PartialEq"); + } + derive.push("Eq"); + derive.push("Debug"); + if !self.allow_alias() { + derive.push("Hash"); + } else { + w.comment("Note: you cannot use pattern matching for enums with allow_alias option"); + } + w.derive(&derive); + let ref type_name = self.type_name; + write_protoc_insertion_point_for_enum( + w, + &self.customize.for_elem, + &self.enum_with_scope.en, + ); + w.expr_block(&format!("pub enum {}", type_name), |w| { + for value in self.values_all() { + write_protoc_insertion_point_for_enum_value( + w, + &self.customize.for_children, + &value.value.proto, + ); + if self.allow_alias() { + w.write_line(&format!( + "{}, // {}", + value.rust_name_inner(), + value.number() + )); + } else { + w.write_line(&format!( + "{} = {},", + value.rust_name_inner(), + value.number() + )); + } + } + }); + } + + fn write_impl_enum_fn_value(&self, w: &mut CodeWriter) { + w.def_fn("value(&self) -> i32", |w| { + if self.allow_alias() { + w.match_expr("*self", |w| { + for value in self.values_all() { + w.case_expr( + &format!("{}", value.rust_name_outer()), + &format!("{}", value.number()), + ); + } + }); + } else { + w.write_line("*self as i32") + } + }); + } + + fn write_impl_enum_const_name(&self, w: &mut CodeWriter) { + w.write_line(&format!( + "const NAME: &'static str = \"{}\";", + self.enum_with_scope.en.name() + )); + } + + fn write_impl_enum_fn_from_i32(&self, w: &mut CodeWriter) { + w.def_fn( + &format!( + "from_i32(value: i32) -> ::std::option::Option<{}>", + self.type_name + ), + |w| { + w.match_expr("value", |w| { + let values = self.values_unique(); + for value in values { + w.write_line(&format!( + "{} => ::std::option::Option::Some({}),", + value.number(), + value.rust_name_outer() + )); + } + w.write_line(&format!("_ => {}", EXPR_NONE)); + }); + }, + ); + } + + fn write_impl_enum_const_values(&self, w: &mut CodeWriter) { + w.write_line(&format!("const VALUES: &'static [{}] = &[", self.type_name)); + w.indented(|w| { + for value in self.values_all() { + w.write_line(&format!("{},", value.rust_name_outer())); + } + }); + w.write_line("];"); + } + + fn write_impl_enum(&self, w: &mut CodeWriter) { + w.impl_for_block( + &format!("{}::Enum", protobuf_crate_path(&self.customize.for_elem)), + &format!("{}", self.type_name), + |w| { + self.write_impl_enum_const_name(w); + w.write_line(""); + self.write_impl_enum_fn_value(w); + w.write_line(""); + self.write_impl_enum_fn_from_i32(w); + w.write_line(""); + self.write_impl_enum_const_values(w); + }, + ); + } + + fn write_impl_enum_full(&self, w: &mut CodeWriter) { + let ref type_name = self.type_name; + w.impl_for_block( + &format!( + "{}::EnumFull", + protobuf_crate_path(&self.customize.for_elem) + ), + &format!("{}", type_name), + |w| { + self.write_impl_enum_full_fn_enum_descriptor(w); + w.write_line(""); + self.write_impl_enum_full_fn_descriptor(w); + }, + ); + } + + fn write_impl_enum_full_fn_enum_descriptor(&self, w: &mut CodeWriter) { + write_fn_descriptor( + &self.enum_with_scope.en, + self.enum_with_scope.scope(), + &self.customize.for_elem, + w, + ); + } + + fn rust_enum_descriptor_is_enum_index(&self) -> bool { + if self.allow_alias() { + false + } else { + self.values_all() + .into_iter() + .enumerate() + .all(|(i, value)| (i as i32) == value.number()) + } + } + + fn write_impl_enum_full_fn_descriptor(&self, w: &mut CodeWriter) { + let sig = format!( + "descriptor(&self) -> {}::reflect::EnumValueDescriptor", + protobuf_crate_path(&self.customize.for_elem) + ); + w.def_fn(&sig, |w| { + if self.rust_enum_descriptor_is_enum_index() { + w.write_line("let index = *self as usize;"); + } else { + w.write_line("let index = match self {"); + w.indented(|w| { + for (i, value) in self.values_all().into_iter().enumerate() { + w.write_line(&format!( + "{}::{} => {},", + self.type_name, + value.rust_name_inner(), + i + )); + } + }); + w.write_line("};"); + } + w.write_line(&format!("Self::enum_descriptor().value_by_index(index)")); + }); + } + + fn write_generated_enum_descriptor_data(&self, w: &mut CodeWriter) { + let sig = format!( + "generated_enum_descriptor_data() -> {}::reflect::GeneratedEnumDescriptorData", + protobuf_crate_path(&self.customize.for_elem) + ); + w.fn_block( + Visibility::Path( + self.enum_with_scope + .scope() + .rust_path_to_file() + .to_reverse(), + ), + &sig, + |w| { + w.write_line(&format!( + "{}::reflect::GeneratedEnumDescriptorData::new::<{}>(\"{}\")", + protobuf_crate_path(&self.customize.for_elem), + self.type_name, + self.enum_with_scope.name_to_package(), + )); + }, + ); + } + + fn write_impl_eq(&self, w: &mut CodeWriter) { + assert!(self.allow_alias()); + w.impl_for_block( + "::std::cmp::PartialEq", + &format!("{}", self.type_name), + |w| { + w.def_fn("eq(&self, other: &Self) -> bool", |w| { + w.write_line(&format!( + "{}::Enum::value(self) == {}::Enum::value(other)", + protobuf_crate_path(&self.customize.for_elem), + protobuf_crate_path(&self.customize.for_elem) + )); + }); + }, + ); + } + + fn write_impl_hash(&self, w: &mut CodeWriter) { + assert!(self.allow_alias()); + w.impl_for_block("::std::hash::Hash", &format!("{}", self.type_name), |w| { + w.def_fn("hash<H : ::std::hash::Hasher>(&self, state: &mut H)", |w| { + w.write_line(&format!( + "state.write_i32({}::Enum::value(self))", + protobuf_crate_path(&self.customize.for_elem) + )); + }); + }); + } + + fn write_impl_default(&self, w: &mut CodeWriter) { + let first_value = &self.enum_with_scope.values()[0]; + if first_value.proto.proto().number() != 0 { + // This warning is emitted only for proto2 + // (because in proto3 first enum variant number is always 0). + // `Default` implemented unconditionally to simplify certain + // generic operations, e. g. reading a map. + // Also, note that even in proto2 some operations fallback to + // first enum value, e. g. `get_xxx` for unset field, + // so this implementation is not completely unreasonable. + w.comment("Note, `Default` is implemented although default value is not 0"); + } + w.impl_for_block( + "::std::default::Default", + &format!("{}", self.type_name), + |w| { + w.def_fn("default() -> Self", |w| { + w.write_line(&format!( + "{}::{}", + &self.type_name, + &first_value.rust_name() + )) + }); + }, + ); + } +} diff --git a/src/gen/extensions.rs b/src/gen/extensions.rs new file mode 100644 index 0000000..bfd4267 --- /dev/null +++ b/src/gen/extensions.rs @@ -0,0 +1,133 @@ +use protobuf::descriptor::*; +use protobuf::reflect::FileDescriptor; +use protobuf_parse::ProtobufAbsPath; + +use crate::customize::ctx::CustomizeElemCtx; +use crate::customize::Customize; +use crate::gen::code_writer::CodeWriter; +use crate::gen::field::rust_field_name_for_protobuf_field_name; +use crate::gen::file_and_mod::FileAndMod; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::message::RustTypeMessage; +use crate::gen::rust::ident_with_path::RustIdentWithPath; +use crate::gen::rust::rel_path::RustRelativePath; +use crate::gen::rust_types_values::*; +use crate::gen::scope::RootScope; + +struct ExtGen<'a> { + file: &'a FileDescriptor, + root_scope: &'a RootScope<'a>, + field: &'a FieldDescriptorProto, + customize: Customize, +} + +impl<'a> ExtGen<'a> { + fn extendee_rust_name(&self) -> RustIdentWithPath { + type_name_to_rust_relative( + &ProtobufAbsPath::from(self.field.extendee()), + &FileAndMod { + file: self.file.proto().name().to_owned(), + relative_mod: RustRelativePath::from("exts"), + customize: self.customize.clone(), + }, + self.root_scope, + ) + } + + fn repeated(&self) -> bool { + match self.field.label() { + field_descriptor_proto::Label::LABEL_REPEATED => true, + field_descriptor_proto::Label::LABEL_OPTIONAL => false, + field_descriptor_proto::Label::LABEL_REQUIRED => { + panic!("required ext field: {}", self.field.name()) + } + } + } + + fn return_type_gen(&self) -> ProtobufTypeGen { + if self.field.has_type_name() { + let rust_name_relative = type_name_to_rust_relative( + &ProtobufAbsPath::from(self.field.type_name()), + &FileAndMod { + file: self.file.proto().name().to_owned(), + relative_mod: RustRelativePath::from("exts"), + customize: self.customize.clone(), + }, + self.root_scope, + ); + match self.field.type_() { + field_descriptor_proto::Type::TYPE_MESSAGE => { + ProtobufTypeGen::Message(RustTypeMessage(rust_name_relative)) + } + field_descriptor_proto::Type::TYPE_ENUM => { + ProtobufTypeGen::EnumOrUnknown(rust_name_relative) + } + t => panic!("unknown type: {:?}", t), + } + } else { + ProtobufTypeGen::Primitive(self.field.type_(), PrimitiveTypeVariant::Default) + } + } + + fn write(&self, w: &mut CodeWriter) { + let suffix = if self.repeated() { + "ExtFieldRepeated" + } else { + "ExtFieldOptional" + }; + let field_type = format!( + "{protobuf_crate}::ext::{suffix}", + protobuf_crate = protobuf_crate_path(&self.customize) + ); + w.pub_const( + &rust_field_name_for_protobuf_field_name(self.field.name()).to_string(), + &format!( + "{field_type}<{extendee}, {rust_type}>", + extendee=self.extendee_rust_name(), + rust_type=self.return_type_gen().protobuf_value(&self.customize), + ), + &format!( + "{field_type}::new({field_number}, {protobuf_crate}::descriptor::field_descriptor_proto::Type::{t:?})", + field_number=self.field.number(), + protobuf_crate = protobuf_crate_path(&self.customize), + t=self.field.type_(), + ), + ); + } +} + +pub(crate) fn write_extensions( + file: &FileDescriptor, + root_scope: &RootScope, + w: &mut CodeWriter, + customize: &CustomizeElemCtx, +) { + if file.proto().extension.is_empty() { + return; + } + + if customize.for_elem.lite_runtime.unwrap_or(false) { + w.write_line(""); + w.comment("Extension generation with lite runtime is not supported"); + return; + } + + w.write_line(""); + w.write_line("/// Extension fields"); + w.pub_mod("exts", |w| { + for field in &file.proto().extension { + if field.type_() == field_descriptor_proto::Type::TYPE_GROUP { + continue; + } + + w.write_line(""); + ExtGen { + file, + root_scope, + field, + customize: customize.for_elem.clone(), + } + .write(w); + } + }); +} diff --git a/src/gen/field/accessor.rs b/src/gen/field/accessor.rs new file mode 100644 index 0000000..26f0ef4 --- /dev/null +++ b/src/gen/field/accessor.rs @@ -0,0 +1,241 @@ +use crate::gen::code_writer::CodeWriter; +use crate::gen::field::elem::FieldElem; +use crate::gen::field::elem::FieldElemEnum; +use crate::gen::field::option_kind::OptionKind; +use crate::gen::field::repeated::RepeatedField; +use crate::gen::field::repeated::RepeatedFieldKind; +use crate::gen::field::singular::SingularField; +use crate::gen::field::singular::SingularFieldFlag; +use crate::gen::field::FieldGen; +use crate::gen::field::FieldKind; +use crate::gen::field::MapField; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::oneof::OneofField; +use crate::gen::rust_types_values::RustType; +use crate::gen::scope::WithScope; + +struct AccessorFn { + name: String, + // function type params after first underscore + type_params: Vec<String>, + callback_params: Vec<String>, +} + +impl AccessorFn { + fn sig(&self) -> String { + let mut s = self.name.clone(); + s.push_str("::<_"); + for p in &self.type_params { + s.push_str(", "); + s.push_str(&p); + } + s.push_str(">"); + s + } +} + +impl FieldGen<'_> { + fn make_accessor_fns_lambda(&self) -> Vec<String> { + let message = self.proto_field.message.rust_name(); + vec![ + format!("|m: &{}| {{ &m.{} }}", message, self.rust_name), + format!("|m: &mut {}| {{ &mut m.{} }}", message, self.rust_name), + ] + } + + fn make_accessor_fns_has_get_set(&self) -> Vec<String> { + let message = self.proto_field.message.rust_name(); + vec![ + format!("{}::{}", message, self.has_name()), + format!("{}::{}", message, self.rust_name), + format!("{}::{}", message, self.set_name()), + ] + } + + fn make_accessor_fns_has_get_mut_set(&self) -> Vec<String> { + let message = self.proto_field.message.rust_name(); + vec![ + format!("{}::{}", message, self.has_name()), + format!("{}::{}", message, self.rust_name), + format!("{}::{}", message, self.mut_name()), + format!("{}::{}", message, self.set_name()), + ] + } + + fn accessor_fn_map(&self, map_field: &MapField) -> AccessorFn { + let MapField { .. } = map_field; + AccessorFn { + name: "make_map_simpler_accessor".to_owned(), + type_params: vec![format!("_"), format!("_")], + callback_params: self.make_accessor_fns_lambda(), + } + } + + fn accessor_fn_repeated(&self, repeated_field: &RepeatedField) -> AccessorFn { + let RepeatedField { .. } = repeated_field; + let name = match repeated_field.kind() { + RepeatedFieldKind::Vec => "make_vec_simpler_accessor", + }; + AccessorFn { + name: name.to_owned(), + type_params: vec![format!("_")], + callback_params: self.make_accessor_fns_lambda(), + } + } + + fn accessor_fn_oneof_enum(&self, oneof: &OneofField, en: &FieldElemEnum) -> AccessorFn { + let message = self.proto_field.message.rust_name(); + + let variant_path = oneof.variant_path(&self.proto_field.message.scope.rust_path_to_file()); + + let getter = CodeWriter::with_no_error(|w| { + w.expr_block( + &format!( + "|message: &{}| match &message.{}", + message, oneof.oneof_field_name + ), + |w| { + w.case_expr( + &format!("::std::option::Option::Some({}(e))", variant_path), + "::std::option::Option::Some(*e)", + ); + w.case_expr("_", "::std::option::Option::None"); + }, + ); + }); + + let setter = CodeWriter::with_no_error(|w| { + w.expr_block( + &format!( + "|message: &mut {}, e: {}::EnumOrUnknown<{}>|", + message, + protobuf_crate_path(&self.customize), + en.enum_rust_type(&self.file_and_mod()) + .to_code(&self.customize) + ), + |w| { + w.write_line(&format!( + "message.{} = ::std::option::Option::Some({}(e));", + oneof.oneof_field_name, variant_path + )); + }, + ) + }); + + let default = self.xxx_default_value_rust(); + + AccessorFn { + name: "make_oneof_enum_accessors".to_owned(), + type_params: vec![format!("_")], + callback_params: vec![getter, setter, default], + } + } + + fn accessor_fn_singular_without_flag(&self, _elem: &FieldElem) -> AccessorFn { + AccessorFn { + name: "make_simpler_field_accessor".to_owned(), + type_params: vec![format!("_")], + callback_params: self.make_accessor_fns_lambda(), + } + } + + fn accessor_fn_singular_with_flag( + &self, + elem: &FieldElem, + _option_kind: OptionKind, + ) -> AccessorFn { + match elem { + FieldElem::Message(m) => AccessorFn { + name: "make_message_field_accessor".to_owned(), + type_params: vec![format!("{}", m.rust_name_relative(&self.file_and_mod()))], + callback_params: self.make_accessor_fns_lambda(), + }, + FieldElem::Primitive(..) | FieldElem::Enum(..) => AccessorFn { + name: "make_option_accessor".to_owned(), + type_params: vec!["_".to_owned()], + callback_params: self.make_accessor_fns_lambda(), + }, + FieldElem::Group => { + unreachable!("no accessor for group field"); + } + } + } + + fn accessor_fn_oneof(&self, oneof: &OneofField) -> AccessorFn { + let OneofField { ref elem, .. } = oneof; + + let reference = self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()); + + if let FieldElem::Enum(en) = &oneof.elem { + return self.accessor_fn_oneof_enum(oneof, en); + } + + if elem.is_copy() { + return AccessorFn { + name: "make_oneof_copy_has_get_set_simpler_accessors".to_owned(), + type_params: vec![format!("_")], + callback_params: self.make_accessor_fns_has_get_set(), + }; + } + + if let RustType::Message(name) = elem.rust_storage_elem_type(&reference) { + return AccessorFn { + name: "make_oneof_message_has_get_mut_set_accessor".to_owned(), + type_params: vec![format!("{}", name)], + callback_params: self.make_accessor_fns_has_get_mut_set(), + }; + } + + // string or bytes + AccessorFn { + name: "make_oneof_deref_has_get_set_simpler_accessor".to_owned(), + type_params: vec![format!("_")], + callback_params: self.make_accessor_fns_has_get_set(), + } + } + + fn accessor_fn(&self) -> AccessorFn { + match self.kind { + FieldKind::Repeated(ref repeated_field) => self.accessor_fn_repeated(repeated_field), + FieldKind::Map(ref map_field) => self.accessor_fn_map(map_field), + FieldKind::Singular(SingularField { + ref elem, + flag: SingularFieldFlag::WithoutFlag, + }) => self.accessor_fn_singular_without_flag(elem), + FieldKind::Singular(SingularField { + ref elem, + flag: SingularFieldFlag::WithFlag { option_kind, .. }, + }) => self.accessor_fn_singular_with_flag(elem, option_kind), + FieldKind::Oneof(ref oneof) => self.accessor_fn_oneof(oneof), + } + } + + pub fn write_push_accessor(&self, fields_var: &str, w: &mut CodeWriter) { + let accessor_fn = self.accessor_fn(); + w.write_line(&format!( + "{}.push({}::reflect::rt::v2::{}(", + fields_var, + protobuf_crate_path(&self.customize), + accessor_fn.sig() + )); + w.indented(|w| { + w.write_line(&format!("\"{}\",", self.proto_field.name())); + for callback in &accessor_fn.callback_params { + let callback_lines: Vec<&str> = callback.lines().collect(); + for (i, callback_line) in callback_lines.iter().enumerate() { + let comma = if i == callback_lines.len() - 1 { + "," + } else { + "" + }; + w.write_line(&format!("{}{}", callback_line, comma)); + } + } + }); + w.write_line("));"); + } +} diff --git a/src/gen/field/elem.rs b/src/gen/field/elem.rs new file mode 100644 index 0000000..fd06b4c --- /dev/null +++ b/src/gen/field/elem.rs @@ -0,0 +1,319 @@ +use protobuf::descriptor::field_descriptor_proto::Type; +use protobuf::reflect::RuntimeFieldType; +use protobuf::rt::tag_size; +use protobuf_parse::ProtobufAbsPath; + +use crate::gen::code_writer::CodeWriter; +use crate::gen::field::type_ext::TypeExt; +use crate::gen::file_and_mod::FileAndMod; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::message::RustTypeMessage; +use crate::gen::rust::ident_with_path::RustIdentWithPath; +use crate::gen::rust_types_values::message_or_enum_to_rust_relative; +use crate::gen::rust_types_values::PrimitiveTypeVariant; +use crate::gen::rust_types_values::RustType; +use crate::gen::rust_types_values::RustValueTyped; +use crate::gen::scope::EnumValueWithContext; +use crate::gen::scope::FieldWithContext; +use crate::gen::scope::MessageOrEnumWithScope; +use crate::gen::scope::MessageWithScope; +use crate::gen::scope::RootScope; +use crate::Customize; + +#[derive(Clone, Debug)] +pub(crate) struct FieldElemEnum<'a> { + /// Enum default value variant, either from proto or from enum definition + default_value: EnumValueWithContext<'a>, +} + +impl<'a> FieldElemEnum<'a> { + fn rust_name_relative(&self, reference: &FileAndMod) -> RustIdentWithPath { + message_or_enum_to_rust_relative(&self.default_value.en, reference) + } + + pub(crate) fn enum_rust_type(&self, reference: &FileAndMod) -> RustType { + RustType::Enum( + self.rust_name_relative(reference), + self.default_value.rust_name(), + self.default_value.proto.proto().number(), + ) + } + + fn enum_or_unknown_rust_type(&self, reference: &FileAndMod) -> RustType { + RustType::EnumOrUnknown( + self.rust_name_relative(reference), + self.default_value.rust_name(), + self.default_value.proto.proto().number(), + ) + } + + pub(crate) fn default_value_rust_expr(&self, reference: &FileAndMod) -> RustIdentWithPath { + self.rust_name_relative(reference) + .to_path() + .with_ident(self.default_value.rust_name()) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct FieldElemMessage<'a> { + pub message: MessageWithScope<'a>, +} + +impl<'a> FieldElemMessage<'a> { + pub(crate) fn rust_name_relative(&self, reference: &FileAndMod) -> RustTypeMessage { + RustTypeMessage(message_or_enum_to_rust_relative(&self.message, reference)) + } + + fn rust_type(&self, reference: &FileAndMod) -> RustType { + RustType::Message(self.rust_name_relative(reference)) + } +} + +#[derive(Clone, Debug)] +pub(crate) enum FieldElem<'a> { + Primitive(Type, PrimitiveTypeVariant), + Message(FieldElemMessage<'a>), + Enum(FieldElemEnum<'a>), + Group, +} + +pub(crate) enum HowToGetMessageSize { + Compute, + GetCached, +} + +impl<'a> FieldElem<'a> { + pub(crate) fn proto_type(&self) -> Type { + match *self { + FieldElem::Primitive(t, ..) => t, + FieldElem::Group => Type::TYPE_GROUP, + FieldElem::Message(..) => Type::TYPE_MESSAGE, + FieldElem::Enum(..) => Type::TYPE_ENUM, + } + } + + pub(crate) fn is_copy(&self) -> bool { + self.proto_type().is_copy() + } + + pub(crate) fn rust_storage_elem_type(&self, reference: &FileAndMod) -> RustType { + match *self { + FieldElem::Primitive(t, PrimitiveTypeVariant::Default) => t.rust_type(), + FieldElem::Primitive(Type::TYPE_STRING, PrimitiveTypeVariant::TokioBytes) => { + RustType::Chars + } + FieldElem::Primitive(Type::TYPE_BYTES, PrimitiveTypeVariant::TokioBytes) => { + RustType::Bytes + } + FieldElem::Primitive(.., PrimitiveTypeVariant::TokioBytes) => unreachable!(), + FieldElem::Group => RustType::Group, + FieldElem::Message(ref m) => m.rust_type(reference), + FieldElem::Enum(ref en) => en.enum_or_unknown_rust_type(reference), + } + } + + // Type of set_xxx function parameter type for singular fields + pub(crate) fn rust_set_xxx_param_type(&self, reference: &FileAndMod) -> RustType { + if let FieldElem::Enum(ref en) = *self { + en.enum_rust_type(reference) + } else { + self.rust_storage_elem_type(reference) + } + } + + pub(crate) fn primitive_type_variant(&self) -> PrimitiveTypeVariant { + match self { + &FieldElem::Primitive(_, v) => v, + _ => PrimitiveTypeVariant::Default, + } + } + + pub(crate) fn singular_field_size( + &self, + field_number: u32, + var: &RustValueTyped, + customize: &Customize, + ) -> String { + let tag_size = tag_size(field_number); + match self.proto_type().encoded_size() { + Some(data_size) => format!("{tag_size} + {data_size}"), + None => match self.proto_type() { + Type::TYPE_MESSAGE => panic!("not a single-liner"), + // We are not inlining `bytes_size` here, + // assuming the compiler is smart enough to do it for us. + // https://rust.godbolt.org/z/GrKa5zxq6 + Type::TYPE_BYTES => format!( + "{}::rt::bytes_size({}, &{})", + protobuf_crate_path(customize), + field_number, + var.value + ), + Type::TYPE_STRING => format!( + "{}::rt::string_size({}, &{})", + protobuf_crate_path(customize), + field_number, + var.value + ), + Type::TYPE_ENUM => { + format!( + "{}::rt::int32_size({}, {}.value())", + protobuf_crate_path(customize), + field_number, + var.value, + ) + } + _ => { + let param_type = match &var.rust_type { + RustType::Ref(t) => (**t).clone(), + t => t.clone(), + }; + let f = match self.proto_type() { + Type::TYPE_SINT32 => "sint32_size", + Type::TYPE_SINT64 => "sint64_size", + Type::TYPE_INT32 => "int32_size", + Type::TYPE_INT64 => "int64_size", + Type::TYPE_UINT32 => "uint32_size", + Type::TYPE_UINT64 => "uint64_size", + t => unreachable!("unexpected type: {:?}", t), + }; + format!( + "{}::rt::{f}({}, {})", + protobuf_crate_path(customize), + field_number, + var.into_type(param_type, customize).value + ) + } + }, + } + } + + pub(crate) fn write_element_size( + &self, + field_number: u32, + item_var: &RustValueTyped, + how_to_get_message_size: HowToGetMessageSize, + sum_var: &str, + customize: &Customize, + w: &mut CodeWriter, + ) { + let tag_size = tag_size(field_number); + match self.proto_type() { + Type::TYPE_MESSAGE => { + match how_to_get_message_size { + HowToGetMessageSize::Compute => { + w.write_line(&format!("let len = {}.compute_size();", item_var.value)) + } + HowToGetMessageSize::GetCached => w.write_line(&format!( + "let len = {}.cached_size() as u64;", + item_var.value + )), + } + w.write_line(&format!( + "{sum_var} += {tag_size} + {}::rt::compute_raw_varint64_size(len) + len;", + protobuf_crate_path(customize), + )); + } + _ => { + w.write_line(&format!( + "{sum_var} += {};", + self.singular_field_size(field_number, item_var, customize) + )); + } + } + } + + pub(crate) fn write_write_element( + &self, + field_number: u32, + v: &RustValueTyped, + file_and_mod: &FileAndMod, + customize: &Customize, + os: &str, + w: &mut CodeWriter, + ) { + match self.proto_type() { + Type::TYPE_MESSAGE => { + let param_type = RustType::Ref(Box::new(self.rust_storage_elem_type(file_and_mod))); + + w.write_line(&format!( + "{}::rt::write_message_field_with_cached_size({}, {}, {})?;", + protobuf_crate_path(customize), + field_number, + v.into_type(param_type, customize).value, + os + )); + } + _ => { + let param_type = self.proto_type().os_write_fn_param_type(); + let os_write_fn_suffix = self.proto_type().protobuf_name(); + w.write_line(&format!( + "{}.write_{}({}, {})?;", + os, + os_write_fn_suffix, + field_number, + v.into_type(param_type, customize).value + )); + } + } + } + + pub(crate) fn read_one_liner(&self) -> String { + format!( + "{}?", + self.proto_type().read("is", self.primitive_type_variant()) + ) + } +} + +pub(crate) fn field_elem<'a>( + field: &FieldWithContext, + root_scope: &'a RootScope<'a>, + customize: &Customize, +) -> FieldElem<'a> { + if let RuntimeFieldType::Map(..) = field.field.runtime_field_type() { + unreachable!(); + } + + if field.field.proto().type_() == Type::TYPE_GROUP { + FieldElem::Group + } else if field.field.proto().has_type_name() { + let message_or_enum = root_scope + .find_message_or_enum(&ProtobufAbsPath::from(field.field.proto().type_name())); + match (field.field.proto().type_(), message_or_enum) { + (Type::TYPE_MESSAGE, MessageOrEnumWithScope::Message(message)) => { + FieldElem::Message(FieldElemMessage { + message: message.clone(), + }) + } + (Type::TYPE_ENUM, MessageOrEnumWithScope::Enum(enum_with_scope)) => { + let default_value = if field.field.proto().has_default_value() { + enum_with_scope.value_by_name(field.field.proto().default_value()) + } else { + enum_with_scope.values()[0].clone() + }; + FieldElem::Enum(FieldElemEnum { default_value }) + } + _ => panic!("unknown named type: {:?}", field.field.proto().type_()), + } + } else if field.field.proto().has_type() { + let tokio_for_bytes = customize.tokio_bytes.unwrap_or(false); + let tokio_for_string = customize.tokio_bytes_for_string.unwrap_or(false); + + let elem = match field.field.proto().type_() { + Type::TYPE_STRING if tokio_for_string => { + FieldElem::Primitive(Type::TYPE_STRING, PrimitiveTypeVariant::TokioBytes) + } + Type::TYPE_BYTES if tokio_for_bytes => { + FieldElem::Primitive(Type::TYPE_BYTES, PrimitiveTypeVariant::TokioBytes) + } + t => FieldElem::Primitive(t, PrimitiveTypeVariant::Default), + }; + + elem + } else { + panic!( + "neither type_name, nor field_type specified for field: {}", + field.field.name() + ); + } +} diff --git a/src/gen/field/mod.rs b/src/gen/field/mod.rs new file mode 100644 index 0000000..d6a5e44 --- /dev/null +++ b/src/gen/field/mod.rs @@ -0,0 +1,1850 @@ +mod accessor; +pub(crate) mod elem; +mod option_kind; +mod repeated; +mod singular; +mod tag; +pub(crate) mod type_ext; + +use protobuf::descriptor::field_descriptor_proto::Type; +use protobuf::descriptor::*; +use protobuf::reflect::ReflectValueRef; +use protobuf::reflect::RuntimeFieldType; +use protobuf::reflect::Syntax; +use protobuf::rt; +use protobuf::rt::WireType; +use protobuf_parse::camel_case; +use protobuf_parse::ProtobufAbsPath; + +use crate::customize::ctx::CustomizeElemCtx; +use crate::customize::rustproto_proto::customize_from_rustproto_for_field; +use crate::customize::Customize; +use crate::gen::code_writer::CodeWriter; +use crate::gen::code_writer::Visibility; +use crate::gen::field::elem::field_elem; +use crate::gen::field::elem::FieldElem; +use crate::gen::field::elem::FieldElemEnum; +use crate::gen::field::elem::HowToGetMessageSize; +use crate::gen::field::option_kind::OptionKind; +use crate::gen::field::repeated::RepeatedField; +use crate::gen::field::singular::SingularField; +use crate::gen::field::singular::SingularFieldFlag; +use crate::gen::field::tag::make_tag; +use crate::gen::field::type_ext::TypeExt; +use crate::gen::file_and_mod::FileAndMod; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::map::map_entry; +use crate::gen::oneof::OneofField; +use crate::gen::protoc_insertion_point::write_protoc_insertion_point_for_field; +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::quote::quote_escape_bytes; +use crate::gen::rust::quote::quote_escape_str; +use crate::gen::rust::snippets::EXPR_NONE; +use crate::gen::rust_types_values::PrimitiveTypeVariant; +use crate::gen::rust_types_values::RustType; +use crate::gen::rust_types_values::RustValueTyped; +use crate::gen::scope::FieldWithContext; +use crate::gen::scope::MessageWithScope; +use crate::gen::scope::RootScope; + +fn field_type_protobuf_name<'a>(field: &'a FieldDescriptorProto) -> &'a str { + if field.has_type_name() { + field.type_name() + } else { + field.type_().protobuf_name() + } +} + +#[derive(Clone)] +pub struct MapField<'a> { + _message: MessageWithScope<'a>, + key: FieldElem<'a>, + value: FieldElem<'a>, +} + +#[derive(Clone)] +pub(crate) enum FieldKind<'a> { + // optional or required + Singular(SingularField<'a>), + // repeated except map + Repeated(RepeatedField<'a>), + // map + Map(MapField<'a>), + // part of oneof + Oneof(OneofField<'a>), +} + +impl<'a> FieldKind<'a> { + pub(crate) fn default( + &self, + customize: &Customize, + reference: &FileAndMod, + const_expr: bool, + ) -> String { + match self { + FieldKind::Singular(s) => s.default_value(customize, reference, const_expr), + FieldKind::Repeated(r) => r.default(), + FieldKind::Oneof(..) => EXPR_NONE.to_owned(), + FieldKind::Map(..) => panic!("map fields cannot have field value"), + } + } +} + +#[derive(Clone)] +pub(crate) enum SingularOrOneofField<'a> { + Singular(SingularField<'a>), + Oneof(OneofField<'a>), +} + +impl<'a> SingularOrOneofField<'a> { + fn elem(&self) -> &FieldElem { + match self { + SingularOrOneofField::Singular(SingularField { ref elem, .. }) => elem, + SingularOrOneofField::Oneof(OneofField { ref elem, .. }) => elem, + } + } + + // Type of `xxx` function for singular type. + pub(crate) fn getter_return_type(&self, reference: &FileAndMod) -> RustType { + let elem = self.elem(); + if let FieldElem::Enum(ref en) = elem { + en.enum_rust_type(reference) + } else if elem.is_copy() { + elem.rust_storage_elem_type(reference) + } else { + elem.rust_storage_elem_type(reference).ref_type() + } + } +} + +// Representation of map entry: key type and value type +#[derive(Clone, Debug)] +pub struct EntryKeyValue<'a>(FieldElem<'a>, FieldElem<'a>); + +#[derive(Clone)] +pub(crate) struct FieldGen<'a> { + syntax: Syntax, + pub proto_field: FieldWithContext<'a>, + // field name in generated code + pub rust_name: RustIdent, + pub proto_type: Type, + wire_type: WireType, + pub kind: FieldKind<'a>, + pub generate_accessors: bool, + pub generate_getter: bool, + customize: Customize, + path: Vec<i32>, + info: Option<&'a SourceCodeInfo>, +} + +impl<'a> FieldGen<'a> { + pub(crate) fn parse( + field: FieldWithContext<'a>, + root_scope: &'a RootScope<'a>, + parent_customize: &CustomizeElemCtx<'a>, + path: Vec<i32>, + info: Option<&'a SourceCodeInfo>, + ) -> anyhow::Result<FieldGen<'a>> { + let customize = parent_customize + .child( + &customize_from_rustproto_for_field(field.field.proto().options.get_or_default()), + &field.field, + ) + .for_elem; + + let syntax = field.message.scope.file_scope.syntax(); + + let field_may_have_custom_default_value = syntax == Syntax::Proto2 + && field.field.proto().label() != field_descriptor_proto::Label::LABEL_REPEATED + && field.field.proto().type_() != Type::TYPE_MESSAGE; + + let generate_accessors = customize + .generate_accessors + .unwrap_or(field_may_have_custom_default_value) + || field.is_oneof(); + + let default_generate_getter = generate_accessors || field_may_have_custom_default_value; + let generate_getter = + customize.generate_getter.unwrap_or(default_generate_getter) || field.is_oneof(); + + let kind = match field.field.runtime_field_type() { + RuntimeFieldType::Map(..) => { + let message = root_scope + .find_message(&ProtobufAbsPath::from(field.field.proto().type_name())); + + let (key, value) = map_entry(&message).unwrap(); + + let key = field_elem(&key, root_scope, &customize); + let value = field_elem(&value, root_scope, &customize); + + FieldKind::Map(MapField { + _message: message, + key, + value, + }) + } + RuntimeFieldType::Repeated(..) => { + let elem = field_elem(&field, root_scope, &customize); + + FieldKind::Repeated(RepeatedField { + elem, + packed: field.field.proto().options.get_or_default().packed(), + }) + } + RuntimeFieldType::Singular(..) => { + let elem = field_elem(&field, root_scope, &customize); + + if let Some(oneof) = field.oneof() { + FieldKind::Oneof(OneofField::parse(&oneof, &field.field, elem, root_scope)) + } else { + let flag = if field.message.scope.file_scope.syntax() == Syntax::Proto3 + && field.field.proto().type_() != field_descriptor_proto::Type::TYPE_MESSAGE + && !field.field.proto().proto3_optional() + { + SingularFieldFlag::WithoutFlag + } else { + let required = field.field.proto().label() + == field_descriptor_proto::Label::LABEL_REQUIRED; + let option_kind = match field.field.proto().type_() { + field_descriptor_proto::Type::TYPE_MESSAGE => OptionKind::MessageField, + _ => OptionKind::Option, + }; + + SingularFieldFlag::WithFlag { + required, + option_kind, + } + }; + FieldKind::Singular(SingularField { elem, flag }) + } + } + }; + + Ok(FieldGen { + syntax: field.message.message.file_descriptor().syntax(), + rust_name: rust_field_name_for_protobuf_field_name(&field.field.name()), + proto_type: field.field.proto().type_(), + wire_type: WireType::for_type(field.field.proto().type_()), + proto_field: field, + kind, + generate_accessors, + generate_getter, + customize, + path, + info, + }) + } + + // for message level + fn file_and_mod(&self) -> FileAndMod { + self.proto_field + .message + .scope + .file_and_mod(self.customize.clone()) + } + + fn tag_size(&self) -> u32 { + rt::tag_size(self.proto_field.number() as u32) as u32 + } + + fn is_singular(&self) -> bool { + match self.kind { + FieldKind::Singular(..) => true, + _ => false, + } + } + + fn is_repeated_packed(&self) -> bool { + match self.kind { + FieldKind::Repeated(RepeatedField { packed: true, .. }) => true, + _ => false, + } + } + + pub(crate) fn elem(&self) -> &FieldElem { + match self.kind { + FieldKind::Singular(SingularField { ref elem, .. }) => &elem, + FieldKind::Repeated(RepeatedField { ref elem, .. }) => &elem, + FieldKind::Oneof(OneofField { ref elem, .. }) => &elem, + FieldKind::Map(..) => unreachable!(), + } + } + + // type of field in struct + pub(crate) fn full_storage_type(&self, reference: &FileAndMod) -> RustType { + match self.kind { + FieldKind::Repeated(ref repeated) => repeated.rust_type(reference), + FieldKind::Map(MapField { + ref key, ref value, .. + }) => RustType::HashMap( + Box::new(key.rust_storage_elem_type(reference)), + Box::new(value.rust_storage_elem_type(reference)), + ), + FieldKind::Singular(ref singular) => singular.rust_storage_type(reference), + FieldKind::Oneof(..) => unreachable!(), + } + } + + // type of `v` in `for v in field` + fn full_storage_iter_elem_type(&self, reference: &FileAndMod) -> RustType { + if let FieldKind::Oneof(ref oneof) = self.kind { + oneof.elem.rust_storage_elem_type(reference) + } else { + self.full_storage_type(reference).iter_elem_type() + } + } + + // suffix `xxx` as in `os.write_xxx_no_tag(..)` + fn os_write_fn_suffix(&self) -> &str { + self.proto_type.protobuf_name() + } + + fn os_write_fn_suffix_with_unknown_for_enum(&self) -> &str { + if self.proto_type == field_descriptor_proto::Type::TYPE_ENUM { + "enum_or_unknown" + } else { + self.os_write_fn_suffix() + } + } + + // for field `foo`, type of param of `fn set_foo(..)` + fn set_xxx_param_type(&self, reference: &FileAndMod) -> RustType { + match self.kind { + FieldKind::Singular(SingularField { ref elem, .. }) + | FieldKind::Oneof(OneofField { ref elem, .. }) => { + elem.rust_set_xxx_param_type(reference) + } + FieldKind::Repeated(..) | FieldKind::Map(..) => self.full_storage_type(reference), + } + } + + // for field `foo`, return type if `fn take_foo(..)` + fn take_xxx_return_type(&self, reference: &FileAndMod) -> RustType { + self.set_xxx_param_type(reference) + } + + // for field `foo`, return type of `fn mut_foo(..)` + fn mut_xxx_return_type(&self, reference: &FileAndMod) -> RustType { + RustType::Ref(Box::new(match self.kind { + FieldKind::Singular(SingularField { ref elem, .. }) + | FieldKind::Oneof(OneofField { ref elem, .. }) => { + elem.rust_storage_elem_type(reference) + } + FieldKind::Repeated(..) | FieldKind::Map(..) => self.full_storage_type(reference), + })) + } + + // for field `foo`, return type of `fn foo(..)` + fn getter_return_type(&self) -> RustType { + let reference = self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()); + match &self.kind { + FieldKind::Singular(s) => { + SingularOrOneofField::Singular(s.clone()).getter_return_type(&reference) + } + FieldKind::Oneof(o) => { + SingularOrOneofField::Oneof(o.clone()).getter_return_type(&reference) + } + FieldKind::Repeated(RepeatedField { ref elem, .. }) => RustType::Ref(Box::new( + RustType::Slice(Box::new(elem.rust_storage_elem_type(&reference))), + )), + FieldKind::Map(..) => RustType::Ref(Box::new(self.full_storage_type(&reference))), + } + } + + // elem data is not stored in heap + pub(crate) fn elem_type_is_copy(&self) -> bool { + self.proto_type.is_copy() + } + + fn defaut_value_from_proto_float(f: f64, type_name: &str) -> String { + if f.is_nan() { + format!("::std::{}::NAN", type_name) + } else if f.is_infinite() { + if f > 0.0 { + format!("::std::{}::INFINITY", type_name) + } else { + format!("::std::{}::NEG_INFINITY", type_name) + } + } else { + format!("{:?}{}", f, type_name) + } + } + + fn singular_or_oneof_default_value_from_proto(&self, elem: &FieldElem) -> Option<String> { + if !self.proto_field.field.proto().has_default_value() { + return None; + } + + let default_value = self.proto_field.field.singular_default_value(); + Some(match default_value { + ReflectValueRef::Bool(b) => format!("{}", b), + ReflectValueRef::I32(v) => format!("{}i32", v), + ReflectValueRef::I64(v) => format!("{}i64", v), + ReflectValueRef::U32(v) => format!("{}u32", v), + ReflectValueRef::U64(v) => format!("{}u64", v), + ReflectValueRef::String(v) => quote_escape_str(v), + ReflectValueRef::Bytes(v) => quote_escape_bytes(v), + ReflectValueRef::F32(v) => Self::defaut_value_from_proto_float(v as f64, "f32"), + ReflectValueRef::F64(v) => Self::defaut_value_from_proto_float(v as f64, "f64"), + ReflectValueRef::Enum(_e, _v) => { + if let &FieldElem::Enum(ref e) = elem { + format!("{}", e.default_value_rust_expr(&self.file_and_mod())) + } else { + unreachable!() + } + } + t => panic!("default value is not implemented for type: {:?}", t), + }) + } + + fn default_value_from_proto(&self) -> Option<String> { + match self.kind { + FieldKind::Oneof(OneofField { ref elem, .. }) + | FieldKind::Singular(SingularField { ref elem, .. }) => { + self.singular_or_oneof_default_value_from_proto(elem) + } + _ => unreachable!(), + } + } + + fn default_value_from_proto_typed(&self) -> Option<RustValueTyped> { + self.default_value_from_proto().map(|v| { + let default_value_type = match self.proto_type { + field_descriptor_proto::Type::TYPE_STRING => RustType::Ref(Box::new(RustType::Str)), + field_descriptor_proto::Type::TYPE_BYTES => { + RustType::Ref(Box::new(RustType::Slice(Box::new(RustType::u8())))) + } + _ => self.full_storage_iter_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ), + }; + + RustValueTyped { + value: v, + rust_type: default_value_type, + } + }) + } + + // default value to be returned from `fn xxx` for field `xxx`. + fn xxx_default_value_rust(&self) -> String { + match self.kind { + FieldKind::Singular(..) | FieldKind::Oneof(..) => { + self.default_value_from_proto().unwrap_or_else(|| { + self.getter_return_type() + .default_value(&self.customize, false) + }) + } + _ => unreachable!(), + } + } + + // default to be assigned to field + fn element_default_value_rust(&self) -> RustValueTyped { + match self.kind { + FieldKind::Singular(..) | FieldKind::Oneof(..) => { + self.default_value_from_proto_typed().unwrap_or_else(|| { + self.elem() + .rust_storage_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) + .default_value_typed(&self.customize, false) + }) + } + _ => unreachable!(), + } + } + + pub(crate) fn reconstruct_def(&self) -> String { + let prefix = match (self.proto_field.field.proto().label(), self.syntax) { + (field_descriptor_proto::Label::LABEL_REPEATED, _) => "repeated ", + (_, Syntax::Proto3) => "", + (field_descriptor_proto::Label::LABEL_OPTIONAL, _) => "optional ", + (field_descriptor_proto::Label::LABEL_REQUIRED, _) => "required ", + }; + format!( + "{}{} {} = {}", + prefix, + field_type_protobuf_name(self.proto_field.field.proto()), + self.proto_field.name(), + self.proto_field.number() + ) + } + + pub(crate) fn write_clear(&self, w: &mut CodeWriter) { + match self.kind { + FieldKind::Oneof(ref o) => { + w.write_line(&format!( + "self.{} = ::std::option::Option::None;", + o.oneof_field_name + )); + } + _ => { + let clear_expr = self + .full_storage_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) + .clear(&self.self_field(), &self.customize); + w.write_line(&format!("{};", clear_expr)); + } + } + } + + // output code that writes single element to stream + pub(crate) fn write_write_element( + &self, + elem: &FieldElem, + w: &mut CodeWriter, + os: &str, + v: &RustValueTyped, + ) { + assert!(!self.is_repeated_packed()); + + elem.write_write_element( + self.proto_field.number() as u32, + v, + &self.file_and_mod(), + &self.customize, + os, + w, + ); + } + + fn self_field(&self) -> String { + format!("self.{}", self.rust_name) + } + + fn self_field_is_some(&self) -> String { + assert!(self.is_singular()); + format!("{}.is_some()", self.self_field()) + } + + fn self_field_is_none(&self) -> String { + assert!(self.is_singular()); + format!("{}.is_none()", self.self_field()) + } + + // field data viewed as Option + fn self_field_as_option(&self, elem: &FieldElem, option_kind: OptionKind) -> RustValueTyped { + match self.full_storage_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) { + RustType::Option(ref e) if e.is_copy() => { + return RustType::Option(e.clone()).value(self.self_field()); + } + _ => {} + }; + + let as_option_type = option_kind.as_ref_type( + elem.rust_storage_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ), + ); + + as_option_type.value(format!("{}.as_ref()", self.self_field())) + } + + pub(crate) fn write_struct_field(&self, w: &mut CodeWriter) { + if self.proto_type == field_descriptor_proto::Type::TYPE_GROUP { + w.comment(&format!("{}: <group>", &self.rust_name)); + } else { + w.all_documentation(self.info, &self.path); + + write_protoc_insertion_point_for_field(w, &self.customize, &self.proto_field.field); + w.field_decl_vis( + Visibility::Public, + &self.rust_name.to_string(), + &self + .full_storage_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) + .to_code(&self.customize), + ); + } + } + + fn write_if_let_self_field_is_some<F>(&self, s: &SingularField, w: &mut CodeWriter, cb: F) + where + F: Fn(&RustValueTyped, &mut CodeWriter), + { + match s { + SingularField { + flag: SingularFieldFlag::WithFlag { option_kind, .. }, + ref elem, + } => { + let var = "v"; + let ref_prefix = match elem + .rust_storage_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) + .is_copy() + { + true => "", + false => "", + }; + let as_option = self.self_field_as_option(elem, *option_kind); + w.if_let_stmt( + &format!("Some({}{})", ref_prefix, var), + &as_option.value, + |w| { + let v = RustValueTyped { + value: var.to_owned(), + rust_type: as_option.rust_type.elem_type(), + }; + cb(&v, w); + }, + ); + } + SingularField { + flag: SingularFieldFlag::WithoutFlag, + ref elem, + } => match *elem { + FieldElem::Primitive(field_descriptor_proto::Type::TYPE_STRING, ..) + | FieldElem::Primitive(field_descriptor_proto::Type::TYPE_BYTES, ..) => { + w.if_stmt(format!("!{}.is_empty()", self.self_field()), |w| { + let v = RustValueTyped { + value: self.self_field(), + rust_type: self.full_storage_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ), + }; + cb(&v, w); + }); + } + _ => { + w.if_stmt( + format!( + "{} != {}", + self.self_field(), + self.full_storage_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()) + ) + .default_value(&self.customize, false) + ), + |w| { + let v = RustValueTyped { + value: self.self_field(), + rust_type: self.full_storage_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ), + }; + cb(&v, w); + }, + ); + } + }, + } + } + + pub(crate) fn write_if_self_field_is_none<F>(&self, w: &mut CodeWriter, cb: F) + where + F: Fn(&mut CodeWriter), + { + let self_field_is_none = self.self_field_is_none(); + w.if_stmt(self_field_is_none, cb) + } + + // repeated or singular + pub(crate) fn write_for_self_field<F>(&self, w: &mut CodeWriter, varn: &str, cb: F) + where + F: Fn(&mut CodeWriter, &RustType), + { + let file_and_mod = self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()); + + match &self.kind { + FieldKind::Oneof(oneof_field) => { + let cond = format!( + "Some({}(ref {}))", + oneof_field.variant_path(&file_and_mod.relative_mod), + varn + ); + w.if_let_stmt( + &cond, + &format!("self.{}", oneof_field.oneof_field_name), + |w| cb(w, &oneof_field.elem.rust_storage_elem_type(&file_and_mod)), + ) + } + _ => { + let v_type = self.full_storage_iter_elem_type(&file_and_mod); + let self_field = self.self_field(); + w.for_stmt(&format!("&{}", self_field), varn, |w| cb(w, &v_type)); + } + } + } + + fn write_self_field_assign(&self, w: &mut CodeWriter, value: &str) { + let self_field = self.self_field(); + w.write_line(&format!("{} = {};", self_field, value)); + } + + fn write_self_field_assign_some(&self, w: &mut CodeWriter, s: &SingularField, value: &str) { + match s { + &SingularField { + flag: SingularFieldFlag::WithFlag { option_kind, .. }, + .. + } => { + self.write_self_field_assign(w, &option_kind.wrap_value(value, &self.customize)); + } + &SingularField { + flag: SingularFieldFlag::WithoutFlag, + .. + } => { + self.write_self_field_assign(w, value); + } + } + } + + fn write_self_field_assign_value_singular( + &self, + w: &mut CodeWriter, + s: &SingularField, + value: &RustValueTyped, + ) { + let SingularField { ref elem, ref flag } = s; + let converted = value.into_type( + elem.rust_storage_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) + .clone(), + &self.customize, + ); + let wrapped = match flag { + SingularFieldFlag::WithoutFlag => converted.value, + SingularFieldFlag::WithFlag { option_kind, .. } => { + option_kind.wrap_value(&converted.value, &self.customize) + } + }; + self.write_self_field_assign(w, &wrapped); + } + + fn write_self_field_assign_value(&self, w: &mut CodeWriter, value: &RustValueTyped) { + match self.kind { + FieldKind::Repeated(..) | FieldKind::Map(..) => { + let converted = value.into_type( + self.full_storage_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ), + &self.customize, + ); + self.write_self_field_assign(w, &converted.value); + } + FieldKind::Singular(ref s) => { + self.write_self_field_assign_value_singular(w, s, value); + } + FieldKind::Oneof(..) => unreachable!(), + } + } + + fn write_self_field_assign_default( + &self, + field_kind: &SingularOrOneofField, + w: &mut CodeWriter, + ) { + match field_kind { + SingularOrOneofField::Oneof(oneof) => { + w.write_line(format!( + "self.{} = ::std::option::Option::Some({}({}))", + oneof.oneof_field_name, + oneof.variant_path(&self.proto_field.message.scope.rust_path_to_file()), + // TODO: default from .proto is not needed here (?) + self.element_default_value_rust() + .into_type( + self.full_storage_iter_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()) + ), + &self.customize + ) + .value + )); + } + SingularOrOneofField::Singular(singular) => { + // Note it is different from C++ protobuf, where field is initialized + // with default value + match singular.flag { + SingularFieldFlag::WithFlag { option_kind, .. } => match option_kind { + OptionKind::MessageField => { + let self_field = self.self_field(); + w.write_line(&format!("{}.set_default();", self_field)); + } + _ => { + self.write_self_field_assign_some( + w, + singular, + &self + .elem() + .rust_storage_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) + .default_value_typed(&self.customize, false) + .into_type( + singular.elem.rust_storage_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ), + &self.customize, + ) + .value, + ); + } + }, + SingularFieldFlag::WithoutFlag => unimplemented!(), + } + } + } + } + + fn self_field_vec_packed_size(&self) -> String { + let fn_name = match self.proto_type { + Type::TYPE_ENUM => "vec_packed_enum_or_unknown_size", + Type::TYPE_SINT32 => "vec_packed_sint32_size", + Type::TYPE_SINT64 => "vec_packed_sint64_size", + Type::TYPE_INT32 => "vec_packed_int32_size", + Type::TYPE_INT64 => "vec_packed_int64_size", + Type::TYPE_UINT32 => "vec_packed_uint32_size", + Type::TYPE_UINT64 => "vec_packed_uint64_size", + Type::TYPE_BOOL => "vec_packed_bool_size", + Type::TYPE_FIXED32 => "vec_packed_fixed32_size", + Type::TYPE_FIXED64 => "vec_packed_fixed64_size", + Type::TYPE_SFIXED32 => "vec_packed_sfixed32_size", + Type::TYPE_SFIXED64 => "vec_packed_sfixed64_size", + Type::TYPE_FLOAT => "vec_packed_float_size", + Type::TYPE_DOUBLE => "vec_packed_double_size", + t => unreachable!("{:?}", t), + }; + format!( + "{}::rt::{fn_name}({}, &{})", + protobuf_crate_path(&self.customize), + self.proto_field.number(), + self.self_field() + ) + } + + pub(crate) fn clear_field_func(&self) -> String { + format!("clear_{}", self.rust_name) + } + + fn write_merge_from_field_message_string_bytes_repeated( + &self, + r: &RepeatedField, + w: &mut CodeWriter, + ) { + let read_fn = match &r.elem { + FieldElem::Message(..) => "read_message", + FieldElem::Primitive(Type::TYPE_STRING, PrimitiveTypeVariant::Default) => "read_string", + FieldElem::Primitive(Type::TYPE_STRING, PrimitiveTypeVariant::TokioBytes) => { + "read_tokio_chars" + } + FieldElem::Primitive(Type::TYPE_BYTES, PrimitiveTypeVariant::Default) => "read_bytes", + FieldElem::Primitive(Type::TYPE_BYTES, PrimitiveTypeVariant::TokioBytes) => { + "read_tokio_bytes" + } + _ => unreachable!("for field {}", self.proto_field.field), + }; + w.write_line(&format!("self.{}.push(is.{}()?);", self.rust_name, read_fn,)); + } + + fn tag_with_wire_type(&self, wire_type: WireType) -> u32 { + make_tag(self.proto_field.number() as u32, wire_type) + } + + fn tag(&self) -> u32 { + self.tag_with_wire_type(self.wire_type) + } + + // Write `merge_from` part for this oneof field + fn write_merge_from_oneof_case_block(&self, o: &OneofField, w: &mut CodeWriter) { + w.case_block(&format!("{}", self.tag()), |w| { + let typed = RustValueTyped { + value: format!( + "{}?", + self.proto_type.read("is", o.elem.primitive_type_variant()) + ), + rust_type: self.full_storage_iter_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ), + }; + + let maybe_boxed = if o.boxed { + typed.boxed(&self.customize) + } else { + typed + }; + + w.write_line(&format!( + "self.{} = ::std::option::Option::Some({}({}));", + o.oneof_field_name, + o.variant_path(&self.proto_field.message.scope.rust_path_to_file()), + maybe_boxed.value + )); + }) + } + + // Write `merge_from` part for this map field + fn write_merge_from_map_case_block(&self, map: &MapField, w: &mut CodeWriter) { + let MapField { key, value, .. } = map; + w.case_block(&format!("{}", self.tag()), |w| { + w.write_line(&format!("let len = is.read_raw_varint32()?;",)); + w.write_line(&format!("let old_limit = is.push_limit(len as u64)?;")); + w.write_line(&format!( + "let mut key = ::std::default::Default::default();" + )); + w.write_line(&format!( + "let mut value = ::std::default::Default::default();" + )); + w.while_block("let Some(tag) = is.read_raw_tag_or_eof()?", |w| { + w.match_block("tag", |w| { + let key_tag = make_tag(1, WireType::for_type(key.proto_type())); + let value_tag = make_tag(2, WireType::for_type(value.proto_type())); + w.case_expr( + &format!("{key_tag}"), + &format!("key = {read}", read = key.read_one_liner()), + ); + w.case_expr( + &format!("{value_tag}"), + &format!("value = {read}", read = value.read_one_liner()), + ); + w.case_expr( + "_", + &format!( + "{protobuf_crate}::rt::skip_field_for_tag(tag, is)?", + protobuf_crate = protobuf_crate_path(&self.customize) + ), + ); + }); + }); + w.write_line(&format!("is.pop_limit(old_limit);")); + w.write_line(&format!( + "{field}.insert(key, value);", + field = self.self_field() + )); + }); + } + + // Write `merge_from` part for this singular field + fn write_merge_from_singular_case_block(&self, s: &SingularField, w: &mut CodeWriter) { + w.case_block(&format!("{}", self.tag()), |w| match s.elem { + FieldElem::Message(..) => { + w.write_line(&format!( + "{}::rt::read_singular_message_into_field(is, &mut self.{})?;", + protobuf_crate_path(&self.customize), + self.rust_name, + )); + } + _ => { + let read_proc = s.elem.read_one_liner(); + self.write_self_field_assign_some(w, s, &read_proc); + } + }) + } + + // Write `merge_from` part for this repeated field + fn write_merge_from_repeated_case_block(&self, w: &mut CodeWriter) { + let field = match self.kind { + FieldKind::Repeated(ref field) => field, + _ => panic!(), + }; + + match field.elem { + FieldElem::Message(..) + | FieldElem::Primitive(field_descriptor_proto::Type::TYPE_STRING, ..) + | FieldElem::Primitive(field_descriptor_proto::Type::TYPE_BYTES, ..) => { + w.case_block(&format!("{}", self.tag()), |w| { + self.write_merge_from_field_message_string_bytes_repeated(field, w); + }) + } + FieldElem::Enum(..) => { + w.case_block( + &format!("{}", self.tag_with_wire_type(WireType::Varint)), + |w| { + w.write_line(&format!( + "self.{}.push(is.read_enum_or_unknown()?);", + self.rust_name, + )); + }, + ); + w.case_block( + &format!("{}", self.tag_with_wire_type(WireType::LengthDelimited)), + |w| { + w.write_line(&format!( + "{}::rt::read_repeated_packed_enum_or_unknown_into(is, &mut self.{})?", + protobuf_crate_path(&self.customize), + self.rust_name, + )); + }, + ); + } + _ => { + assert_ne!(self.wire_type, WireType::LengthDelimited); + w.case_block( + &format!("{}", self.tag_with_wire_type(WireType::LengthDelimited)), + |w| { + w.write_line(&format!( + "is.read_repeated_packed_{}_into(&mut self.{})?;", + self.proto_type.protobuf_name(), + self.rust_name + )); + }, + ); + w.case_block(&format!("{}", self.tag()), |w| { + w.write_line(&format!( + "self.{}.push(is.read_{}()?);", + self.rust_name, + self.proto_type.protobuf_name(), + )); + }); + } + } + } + + /// Write `merge_from` part for this field + pub(crate) fn write_merge_from_field_case_block(&self, w: &mut CodeWriter) { + match &self.kind { + FieldKind::Oneof(oneof) => self.write_merge_from_oneof_case_block(oneof, w), + FieldKind::Map(map) => self.write_merge_from_map_case_block(map, w), + FieldKind::Singular(ref s) => self.write_merge_from_singular_case_block(s, w), + FieldKind::Repeated(..) => self.write_merge_from_repeated_case_block(w), + } + } + + pub(crate) fn write_element_size( + &self, + elem: &FieldElem, + w: &mut CodeWriter, + item_var: &RustValueTyped, + sum_var: &str, + ) { + assert!(!self.is_repeated_packed()); + + elem.write_element_size( + self.proto_field.number() as u32, + item_var, + HowToGetMessageSize::Compute, + sum_var, + &self.customize, + w, + ); + } + + fn write_write_map_field( + &self, + key: &FieldElem, + value: &FieldElem, + os: &str, + w: &mut CodeWriter, + ) { + self.for_each_map_entry(key, value, w, |k, v, w| { + w.write_line("let mut entry_size = 0;"); + key.write_element_size( + 1, + k, + HowToGetMessageSize::GetCached, + "entry_size", + &self.customize, + w, + ); + value.write_element_size( + 2, + v, + HowToGetMessageSize::GetCached, + "entry_size", + &self.customize, + w, + ); + w.write_line(&format!( + "{os}.write_raw_varint32({tag})?; // Tag.", + tag = make_tag(self.proto_field.number() as u32, WireType::LengthDelimited), + )); + w.write_line(&format!("{os}.write_raw_varint32(entry_size as u32)?;",)); + key.write_write_element(1, k, &self.file_and_mod(), &self.customize, os, w); + value.write_write_element(2, v, &self.file_and_mod(), &self.customize, os, w); + }); + } + + pub(crate) fn write_message_write_field(&self, os: &str, w: &mut CodeWriter) { + match &self.kind { + FieldKind::Singular(s @ SingularField { elem, .. }) => { + self.write_if_let_self_field_is_some(s, w, |v, w| { + self.write_write_element(&elem, w, os, &v); + }); + } + FieldKind::Repeated(RepeatedField { + packed: false, + elem, + .. + }) => { + self.write_for_self_field(w, "v", |w, v_type| { + let v = RustValueTyped { + value: "v".to_owned(), + rust_type: v_type.clone(), + }; + self.write_write_element(elem, w, "os", &v); + }); + } + FieldKind::Repeated(RepeatedField { packed: true, .. }) => { + w.write_line(&format!( + "os.write_repeated_packed_{}({}, &{})?;", + self.os_write_fn_suffix_with_unknown_for_enum(), + self.proto_field.number(), + self.self_field() + )); + } + FieldKind::Map(MapField { key, value, .. }) => { + self.write_write_map_field(key, value, os, w) + } + FieldKind::Oneof(..) => unreachable!(), + }; + } + + fn for_each_map_entry( + &self, + key: &FieldElem, + value: &FieldElem, + w: &mut CodeWriter, + cb: impl FnOnce(&RustValueTyped, &RustValueTyped, &mut CodeWriter), + ) { + w.for_stmt(&format!("&{}", self.self_field()), "(k, v)", move |w| { + let k = RustValueTyped { + value: "k".to_owned(), + rust_type: key.rust_storage_elem_type(&self.file_and_mod()).wrap_ref(), + }; + let v = RustValueTyped { + value: "v".to_owned(), + rust_type: value + .rust_storage_elem_type(&self.file_and_mod()) + .wrap_ref(), + }; + cb(&k, &v, w) + }); + } + + fn write_compute_map_field_size( + &self, + sum_var: &str, + key: &FieldElem<'a>, + value: &FieldElem<'a>, + w: &mut CodeWriter, + ) { + self.for_each_map_entry(key, value, w, |k, v, w| { + w.write_line("let mut entry_size = 0;"); + key.write_element_size(1, k, HowToGetMessageSize::Compute, "entry_size", &self.customize, w); + value.write_element_size(2, v, HowToGetMessageSize::Compute, "entry_size", &self.customize, w); + w.write_line(&format!("{sum_var} += {tag_size} + {protobuf_crate}::rt::compute_raw_varint64_size(entry_size) + entry_size", + tag_size = self.tag_size(), + protobuf_crate = protobuf_crate_path(&self.customize), + )); + }); + } + + pub(crate) fn write_message_compute_field_size(&self, sum_var: &str, w: &mut CodeWriter) { + match &self.kind { + FieldKind::Singular(s @ SingularField { elem, .. }) => { + self.write_if_let_self_field_is_some(s, w, |v, w| { + self.write_element_size(&elem, w, v, sum_var) + }); + } + FieldKind::Repeated(RepeatedField { + packed: false, + elem, + .. + }) => { + match elem.proto_type().encoded_size() { + Some(s) => { + let tag_size = self.tag_size(); + let self_field = self.self_field(); + w.write_line(&format!( + "{} += {} * {}.len() as u64;", + sum_var, + (s + tag_size) as isize, + self_field + )); + } + None => { + self.write_for_self_field(w, "value", |w, value_type| { + self.write_element_size( + elem, + w, + &RustValueTyped { + value: "value".to_owned(), + rust_type: value_type.clone(), + }, + sum_var, + ); + }); + } + }; + } + FieldKind::Repeated(RepeatedField { packed: true, .. }) => { + let size_expr = self.self_field_vec_packed_size(); + w.write_line(&format!("{} += {};", sum_var, size_expr)); + } + FieldKind::Map(MapField { key, value, .. }) => { + self.write_compute_map_field_size(sum_var, key, value, w) + } + FieldKind::Oneof(..) => unreachable!(), + } + } + + fn write_message_field_get_singular_message(&self, s: &SingularField, w: &mut CodeWriter) { + match s.flag { + SingularFieldFlag::WithoutFlag => unimplemented!(), + SingularFieldFlag::WithFlag { option_kind, .. } => { + let self_field = self.self_field(); + let ref field_type_name = self.elem().rust_storage_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ); + w.write_line(option_kind.unwrap_ref_or_else( + &format!("{}.as_ref()", self_field), + &format!( + "<{} as {}::Message>::default_instance()", + field_type_name.to_code(&self.customize), + protobuf_crate_path(&self.customize), + ), + )); + } + } + } + + fn write_message_field_get_singular_enum( + &self, + flag: SingularFieldFlag, + _elem: &FieldElemEnum, + w: &mut CodeWriter, + ) { + match flag { + SingularFieldFlag::WithoutFlag => { + w.write_line(&format!("self.{}.enum_value_or_default()", self.rust_name)); + } + SingularFieldFlag::WithFlag { .. } => { + w.match_expr(&self.self_field(), |w| { + let default_value = self.xxx_default_value_rust(); + w.case_expr("Some(e)", &format!("e.enum_value_or({})", default_value)); + w.case_expr("None", &format!("{}", default_value)); + }); + } + } + } + + fn write_message_field_get_singular(&self, singular: &SingularField, w: &mut CodeWriter) { + let get_xxx_return_type = self.getter_return_type(); + + match singular.elem { + FieldElem::Message(..) => self.write_message_field_get_singular_message(singular, w), + FieldElem::Enum(ref en) => { + self.write_message_field_get_singular_enum(singular.flag, en, w) + } + _ => { + let get_xxx_default_value_rust = self.xxx_default_value_rust(); + let self_field = self.self_field(); + match singular { + &SingularField { + ref elem, + flag: SingularFieldFlag::WithFlag { option_kind, .. }, + .. + } => { + if get_xxx_return_type.is_ref().is_some() { + let as_option = self.self_field_as_option(elem, option_kind); + w.match_expr(&as_option.value, |w| { + let v_type = as_option.rust_type.elem_type(); + let r_type = self.getter_return_type(); + w.case_expr( + "Some(v)", + v_type.into_target(&r_type, "v", &self.customize), + ); + let get_xxx_default_value_rust = self.xxx_default_value_rust(); + w.case_expr("None", get_xxx_default_value_rust); + }); + } else { + w.write_line(&format!( + "{}.unwrap_or({})", + self_field, get_xxx_default_value_rust + )); + } + } + &SingularField { + flag: SingularFieldFlag::WithoutFlag, + .. + } => { + w.write_line( + self.full_storage_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) + .into_target( + &get_xxx_return_type, + &self_field, + &self.customize, + ), + ); + } + } + } + } + } + + fn write_message_field_get_oneof(&self, o: &OneofField, w: &mut CodeWriter) { + let get_xxx_return_type = SingularOrOneofField::Oneof(o.clone()).getter_return_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ); + let OneofField { ref elem, .. } = o; + w.match_expr(&format!("self.{}", o.oneof_field_name), |w| { + let (refv, vtype) = if !elem.is_copy() { + ( + "ref v", + elem.rust_storage_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) + .ref_type(), + ) + } else { + ( + "v", + elem.rust_storage_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ), + ) + }; + w.case_expr( + format!( + "::std::option::Option::Some({}({}))", + o.variant_path(&self.proto_field.message.scope.rust_path_to_file()), + refv + ), + vtype.into_target(&get_xxx_return_type, "v", &self.customize), + ); + w.case_expr("_", self.xxx_default_value_rust()); + }); + } + + fn write_message_field_get(&self, w: &mut CodeWriter) { + let get_xxx_return_type = self.getter_return_type(); + let fn_def = format!( + "{}(&self) -> {}", + self.rust_name, + get_xxx_return_type.to_code(&self.customize) + ); + + w.pub_fn(&fn_def, |w| match self.kind { + FieldKind::Oneof(ref o) => { + self.write_message_field_get_oneof(o, w); + } + FieldKind::Singular(ref s) => { + self.write_message_field_get_singular(s, w); + } + FieldKind::Repeated(..) | FieldKind::Map(..) => { + let self_field = self.self_field(); + w.write_line(&format!("&{}", self_field)); + } + }); + } + + fn has_has(&self) -> bool { + match self.kind { + FieldKind::Repeated(..) | FieldKind::Map(..) => false, + FieldKind::Singular(SingularField { + flag: SingularFieldFlag::WithFlag { .. }, + .. + }) => true, + FieldKind::Singular(SingularField { + flag: SingularFieldFlag::WithoutFlag, + .. + }) => false, + FieldKind::Oneof(..) => true, + } + } + + fn has_mut(&self) -> bool { + match self.kind { + FieldKind::Repeated(..) | FieldKind::Map(..) => true, + // TODO: string should be public, and mut is not needed + FieldKind::Singular(..) | FieldKind::Oneof(..) => !self.elem_type_is_copy(), + } + } + + fn has_take(&self) -> bool { + match self.kind { + FieldKind::Repeated(..) | FieldKind::Map(..) => true, + // TODO: string should be public, and mut is not needed + FieldKind::Singular(..) | FieldKind::Oneof(..) => !self.elem_type_is_copy(), + } + } + + fn has_name(&self) -> RustIdent { + RustIdent::new(&format!("has_{}", self.rust_name.get())) + } + + fn set_name(&self) -> RustIdent { + RustIdent::new(&format!("set_{}", self.rust_name.get())) + } + + fn mut_name(&self) -> RustIdent { + RustIdent::new(&format!("mut_{}", self.rust_name.get())) + } + + fn write_message_field_has(&self, w: &mut CodeWriter) { + w.pub_fn( + &format!("{}(&self) -> bool", self.has_name()), + |w| match self.kind { + FieldKind::Oneof(ref oneof) => { + w.match_expr(&format!("self.{}", oneof.oneof_field_name), |w| { + w.case_expr( + format!( + "::std::option::Option::Some({}(..))", + oneof.variant_path( + &self.proto_field.message.scope.rust_path_to_file() + ) + ), + "true", + ); + w.case_expr("_", "false"); + }); + } + _ => { + let self_field_is_some = self.self_field_is_some(); + w.write_line(self_field_is_some); + } + }, + ); + } + + fn write_message_field_set(&self, w: &mut CodeWriter) { + let set_xxx_param_type = self.set_xxx_param_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ); + w.comment("Param is passed by value, moved"); + w.pub_fn( + &format!( + "{}(&mut self, v: {})", + self.set_name(), + set_xxx_param_type.to_code(&self.customize) + ), + |w| { + let value_typed = RustValueTyped { + value: "v".to_owned(), + rust_type: set_xxx_param_type.clone(), + }; + match self.kind { + FieldKind::Oneof(ref oneof) => { + let v = set_xxx_param_type.into_target( + &oneof.rust_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ), + "v", + &self.customize, + ); + w.write_line(&format!( + "self.{} = ::std::option::Option::Some({}({}))", + oneof.oneof_field_name, + oneof.variant_path(&self.proto_field.message.scope.rust_path_to_file()), + v + )); + } + _ => { + self.write_self_field_assign_value(w, &value_typed); + } + } + }, + ); + } + + fn write_message_field_mut_singular_with_flag( + &self, + s: &SingularField, + option_kind: OptionKind, + w: &mut CodeWriter, + ) { + let self_field = self.self_field(); + match option_kind { + OptionKind::MessageField => { + w.write_line(&format!("{}.mut_or_insert_default()", self_field)) + } + OptionKind::Option => { + self.write_if_self_field_is_none(w, |w| { + self.write_self_field_assign_default( + &SingularOrOneofField::Singular(s.clone()), + w, + ); + }); + w.write_line(&format!("{}.as_mut().unwrap()", self_field)); + } + } + } + + fn write_message_field_mut_singular(&self, s: &SingularField, w: &mut CodeWriter) { + match s { + s @ SingularField { + flag: SingularFieldFlag::WithFlag { option_kind, .. }, + .. + } => self.write_message_field_mut_singular_with_flag(s, *option_kind, w), + SingularField { + flag: SingularFieldFlag::WithoutFlag, + .. + } => w.write_line(&format!("&mut {}", self.self_field())), + } + } + + fn write_message_field_mut(&self, w: &mut CodeWriter) { + let mut_xxx_return_type = self.mut_xxx_return_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ); + w.comment("Mutable pointer to the field."); + if self.is_singular() { + w.comment("If field is not initialized, it is initialized with default value first."); + } + let fn_def = match mut_xxx_return_type { + RustType::Ref(ref param) => format!( + "{}(&mut self) -> &mut {}", + self.mut_name(), + param.to_code(&self.customize) + ), + _ => panic!( + "not a ref: {}", + mut_xxx_return_type.to_code(&self.customize) + ), + }; + w.pub_fn(&fn_def, |w| { + match self.kind { + FieldKind::Repeated(..) | FieldKind::Map(..) => { + let self_field = self.self_field(); + w.write_line(&format!("&mut {}", self_field)); + } + FieldKind::Singular(ref s) => { + self.write_message_field_mut_singular(s, w); + } + FieldKind::Oneof(ref o) => { + let self_field_oneof = format!("self.{}", o.oneof_field_name); + + // if oneof does not contain current field + w.if_let_else_stmt( + &format!( + "::std::option::Option::Some({}(_))", + o.variant_path(&self.proto_field.message.scope.rust_path_to_file()) + )[..], + &self_field_oneof[..], + |w| { + // initialize it with default value + w.write_line(&format!( + "{} = ::std::option::Option::Some({}({}));", + self_field_oneof, + o.variant_path(&self.proto_field.message.scope.rust_path_to_file()), + self.element_default_value_rust() + .into_type( + o.rust_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()) + ), + &self.customize + ) + .value + )); + }, + ); + + // extract field + w.match_expr(self_field_oneof, |w| { + w.case_expr( + format!( + "::std::option::Option::Some({}(ref mut v))", + o.variant_path(&self.proto_field.message.scope.rust_path_to_file()) + ), + "v", + ); + w.case_expr("_", "panic!()"); + }); + } + } + }); + } + + fn write_message_field_take_oneof(&self, o: &OneofField, w: &mut CodeWriter) { + let take_xxx_return_type = self.take_xxx_return_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ); + + // TODO: replace with if let + w.write_line(&format!("if self.{}() {{", self.has_name())); + w.indented(|w| { + let self_field_oneof = format!("self.{}", o.oneof_field_name); + w.match_expr(format!("{}.take()", self_field_oneof), |w| { + let value_in_some = o + .rust_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) + .value("v".to_owned()); + let converted = value_in_some.into_type( + self.take_xxx_return_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ), + &self.customize, + ); + w.case_expr( + format!( + "::std::option::Option::Some({}(v))", + o.variant_path(&self.proto_field.message.scope.rust_path_to_file()) + ), + &converted.value, + ); + w.case_expr("_", "panic!()"); + }); + }); + w.write_line("} else {"); + w.indented(|w| { + w.write_line( + self.elem() + .rust_storage_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) + .default_value_typed(&self.customize, false) + .into_type(take_xxx_return_type.clone(), &self.customize) + .value, + ); + }); + w.write_line("}"); + } + + fn write_message_field_take_singular(&self, s: &SingularField, w: &mut CodeWriter) { + match s { + SingularField { + ref elem, + flag: SingularFieldFlag::WithFlag { option_kind, .. }, + } => { + if !elem.is_copy() { + w.write_line( + &option_kind.unwrap_or_else( + &format!("{}.take()", self.self_field()), + &elem + .rust_storage_elem_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ) + .default_value(&self.customize, false), + ), + ); + } else { + w.write_line(&format!( + "{}.take().unwrap_or({})", + self.self_field(), + self.element_default_value_rust().value + )); + } + } + SingularField { + flag: SingularFieldFlag::WithoutFlag, + .. + } => w.write_line(&format!( + "::std::mem::replace(&mut {}, {})", + self.self_field(), + self.full_storage_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()) + ) + .default_value(&self.customize, false) + )), + } + } + + fn write_message_field_take(&self, w: &mut CodeWriter) { + let take_xxx_return_type = self.take_xxx_return_type( + &self + .proto_field + .message + .scope + .file_and_mod(self.customize.clone()), + ); + w.comment("Take field"); + w.pub_fn( + &format!( + "take_{}(&mut self) -> {}", + self.rust_name, + take_xxx_return_type.to_code(&self.customize) + ), + |w| match self.kind { + FieldKind::Singular(ref s) => self.write_message_field_take_singular(&s, w), + FieldKind::Oneof(ref o) => self.write_message_field_take_oneof(o, w), + FieldKind::Repeated(..) | FieldKind::Map(..) => { + w.write_line(&format!( + "::std::mem::replace(&mut self.{}, {})", + self.rust_name, + take_xxx_return_type.default_value(&self.customize, false) + )); + } + }, + ); + } + + pub(crate) fn write_message_single_field_accessors(&self, w: &mut CodeWriter) { + if self.generate_accessors || self.generate_getter { + w.write_line(""); + let reconstruct_def = self.reconstruct_def(); + w.comment(&(reconstruct_def + ";")); + } + + if self.generate_getter { + w.write_line(""); + self.write_message_field_get(w); + } + + if !self.generate_accessors { + return; + } + + w.write_line(""); + let clear_field_func = self.clear_field_func(); + w.pub_fn(&format!("{}(&mut self)", clear_field_func), |w| { + self.write_clear(w); + }); + + if self.has_has() { + w.write_line(""); + self.write_message_field_has(w); + } + + w.write_line(""); + self.write_message_field_set(w); + + if self.has_mut() { + w.write_line(""); + self.write_message_field_mut(w); + } + + if self.has_take() { + w.write_line(""); + self.write_message_field_take(w); + } + } +} + +pub(crate) fn rust_field_name_for_protobuf_field_name(name: &str) -> RustIdent { + RustIdent::new(name) +} + +pub(crate) fn rust_variant_name_for_protobuf_oneof_field_name(name: &str) -> RustIdent { + let name = camel_case(name); + RustIdent::new(&name) +} diff --git a/src/gen/field/option_kind.rs b/src/gen/field/option_kind.rs new file mode 100644 index 0000000..0fde11a --- /dev/null +++ b/src/gen/field/option_kind.rs @@ -0,0 +1,59 @@ +use crate::gen::inside::protobuf_crate_path; +use crate::gen::rust_types_values::RustType; +use crate::Customize; + +/// Optional fields can be stored are `Option<T>` or `SingularPtrField<T>`. +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum OptionKind { + /// Field is `Option<T>` + Option, + /// Field is `SingularPtrField<T>` + MessageField, +} + +impl OptionKind { + pub(crate) fn wrap_element(&self, element_type: RustType) -> RustType { + let element_type = Box::new(element_type); + match self { + OptionKind::Option => RustType::Option(element_type), + OptionKind::MessageField => RustType::MessageField(element_type), + } + } + + // Type of `as_ref()` operation + pub(crate) fn as_ref_type(&self, element_type: RustType) -> RustType { + match self { + OptionKind::Option => RustType::Option(Box::new(element_type.ref_type())), + OptionKind::MessageField => RustType::MessageField(Box::new(element_type.ref_type())), + } + } + + fn _as_option_ref(&self, v: &str) -> String { + match self { + OptionKind::Option | OptionKind::MessageField => format!("{}.as_ref()", v), + } + } + + pub(crate) fn unwrap_or_else(&self, what: &str, default_value: &str) -> String { + match self { + _ => format!("{}.unwrap_or_else(|| {})", what, default_value), + } + } + + pub(crate) fn unwrap_ref_or_else(&self, what: &str, default_value: &str) -> String { + match self { + _ => format!("{}.unwrap_or_else(|| {})", what, default_value), + } + } + + pub(crate) fn wrap_value(&self, value: &str, customize: &Customize) -> String { + match self { + OptionKind::Option => format!("::std::option::Option::Some({})", value), + OptionKind::MessageField => format!( + "{}::MessageField::some({})", + protobuf_crate_path(customize), + value + ), + } + } +} diff --git a/src/gen/field/repeated.rs b/src/gen/field/repeated.rs new file mode 100644 index 0000000..56e0d0b --- /dev/null +++ b/src/gen/field/repeated.rs @@ -0,0 +1,46 @@ +use crate::gen::field::elem::FieldElem; +use crate::gen::file_and_mod::FileAndMod; +use crate::gen::rust::snippets::EXPR_VEC_NEW; +use crate::gen::rust_types_values::RustType; + +/// Repeated field can be `Vec<T>` or `RepeatedField<T>`. +#[derive(Eq, PartialEq, Copy, Clone)] +pub enum RepeatedFieldKind { + Vec, +} + +impl RepeatedFieldKind { + fn wrap_element(&self, element_type: RustType) -> RustType { + let element_type = Box::new(element_type); + match self { + RepeatedFieldKind::Vec => RustType::Vec(element_type), + } + } + + fn default(&self) -> String { + match self { + RepeatedFieldKind::Vec => EXPR_VEC_NEW.to_owned(), + } + } +} + +#[derive(Clone)] +pub(crate) struct RepeatedField<'a> { + pub elem: FieldElem<'a>, + pub packed: bool, +} + +impl<'a> RepeatedField<'a> { + pub(crate) fn kind(&self) -> RepeatedFieldKind { + RepeatedFieldKind::Vec + } + + pub(crate) fn rust_type(&self, reference: &FileAndMod) -> RustType { + self.kind() + .wrap_element(self.elem.rust_storage_elem_type(reference)) + } + + pub(crate) fn default(&self) -> String { + self.kind().default() + } +} diff --git a/src/gen/field/singular.rs b/src/gen/field/singular.rs new file mode 100644 index 0000000..3bd223f --- /dev/null +++ b/src/gen/field/singular.rs @@ -0,0 +1,52 @@ +use crate::gen::field::elem::FieldElem; +use crate::gen::field::option_kind::OptionKind; +use crate::gen::file_and_mod::FileAndMod; +use crate::gen::rust_types_values::RustType; +use crate::Customize; + +#[derive(Clone, PartialEq, Eq, Copy)] +pub enum SingularFieldFlag { + // proto2 or proto3 message + WithFlag { + required: bool, + option_kind: OptionKind, + }, + // proto3 + WithoutFlag, +} + +impl SingularFieldFlag { + pub fn is_required(&self) -> bool { + match *self { + SingularFieldFlag::WithFlag { required, .. } => required, + SingularFieldFlag::WithoutFlag => false, + } + } +} + +#[derive(Clone)] +pub(crate) struct SingularField<'a> { + pub flag: SingularFieldFlag, + pub elem: FieldElem<'a>, +} + +impl<'a> SingularField<'a> { + pub(crate) fn rust_storage_type(&self, reference: &FileAndMod) -> RustType { + match self.flag { + SingularFieldFlag::WithFlag { option_kind, .. } => { + option_kind.wrap_element(self.elem.rust_storage_elem_type(reference)) + } + SingularFieldFlag::WithoutFlag => self.elem.rust_storage_elem_type(reference), + } + } + + pub(crate) fn default_value( + &self, + customize: &Customize, + reference: &FileAndMod, + const_expr: bool, + ) -> String { + self.rust_storage_type(reference) + .default_value(customize, const_expr) + } +} diff --git a/src/gen/field/tag.rs b/src/gen/field/tag.rs new file mode 100644 index 0000000..401a140 --- /dev/null +++ b/src/gen/field/tag.rs @@ -0,0 +1,5 @@ +use protobuf::rt::WireType; + +pub(crate) fn make_tag(field_number: u32, wire_type: WireType) -> u32 { + (field_number << 3) | (wire_type as u32) +} diff --git a/src/gen/field/type_ext.rs b/src/gen/field/type_ext.rs new file mode 100644 index 0000000..334168f --- /dev/null +++ b/src/gen/field/type_ext.rs @@ -0,0 +1,115 @@ +use protobuf::descriptor::field_descriptor_proto::Type; + +use crate::gen::rust_types_values::PrimitiveTypeVariant; +use crate::gen::rust_types_values::RustType; + +pub(crate) trait TypeExt { + fn read(&self, is: &str, primitive_type_variant: PrimitiveTypeVariant) -> String; + fn is_s_varint(&self) -> bool; + fn is_copy(&self) -> bool; + fn protobuf_name(&self) -> &'static str; + fn rust_type(&self) -> RustType; + fn os_write_fn_param_type(&self) -> RustType; + fn encoded_size(&self) -> Option<u32>; +} + +impl TypeExt for Type { + fn read(&self, is: &str, primitive_type_variant: PrimitiveTypeVariant) -> String { + match (self, primitive_type_variant) { + (Type::TYPE_ENUM, _) => format!("{}.read_enum_or_unknown()", is), + (Type::TYPE_BYTES, PrimitiveTypeVariant::TokioBytes) => { + format!("{}.read_tokio_bytes()", is) + } + (Type::TYPE_STRING, PrimitiveTypeVariant::TokioBytes) => { + format!("{}.read_tokio_chars()", is) + } + _ => format!("{}.read_{}()", is, self.protobuf_name()), + } + } + + /// True if self is signed integer with zigzag encoding + fn is_s_varint(&self) -> bool { + match *self { + Type::TYPE_SINT32 | Type::TYPE_SINT64 => true, + _ => false, + } + } + + fn is_copy(&self) -> bool { + match self { + Type::TYPE_MESSAGE | Type::TYPE_STRING | Type::TYPE_BYTES => false, + _ => true, + } + } + + fn protobuf_name(&self) -> &'static str { + match self { + Type::TYPE_DOUBLE => "double", + Type::TYPE_FLOAT => "float", + Type::TYPE_INT32 => "int32", + Type::TYPE_INT64 => "int64", + Type::TYPE_UINT32 => "uint32", + Type::TYPE_UINT64 => "uint64", + Type::TYPE_SINT32 => "sint32", + Type::TYPE_SINT64 => "sint64", + Type::TYPE_FIXED32 => "fixed32", + Type::TYPE_FIXED64 => "fixed64", + Type::TYPE_SFIXED32 => "sfixed32", + Type::TYPE_SFIXED64 => "sfixed64", + Type::TYPE_BOOL => "bool", + Type::TYPE_STRING => "string", + Type::TYPE_BYTES => "bytes", + Type::TYPE_ENUM => "enum", + Type::TYPE_MESSAGE => "message", + Type::TYPE_GROUP => "group", + } + } + + /// Rust type for protobuf base type. + fn rust_type(&self) -> RustType { + match self { + Type::TYPE_DOUBLE => RustType::Float(64), + Type::TYPE_FLOAT => RustType::Float(32), + Type::TYPE_INT32 => RustType::Int(true, 32), + Type::TYPE_INT64 => RustType::Int(true, 64), + Type::TYPE_UINT32 => RustType::Int(false, 32), + Type::TYPE_UINT64 => RustType::Int(false, 64), + Type::TYPE_SINT32 => RustType::Int(true, 32), + Type::TYPE_SINT64 => RustType::Int(true, 64), + Type::TYPE_FIXED32 => RustType::Int(false, 32), + Type::TYPE_FIXED64 => RustType::Int(false, 64), + Type::TYPE_SFIXED32 => RustType::Int(true, 32), + Type::TYPE_SFIXED64 => RustType::Int(true, 64), + Type::TYPE_BOOL => RustType::Bool, + Type::TYPE_STRING => RustType::String, + Type::TYPE_BYTES => RustType::Vec(Box::new(RustType::u8())), + Type::TYPE_ENUM | Type::TYPE_GROUP | Type::TYPE_MESSAGE => { + panic!("there is no rust name for {:?}", self) + } + } + } + + // type of `v` in `os.write_xxx_no_tag(v)` + fn os_write_fn_param_type(&self) -> RustType { + match self { + Type::TYPE_STRING => RustType::amp_str(), + Type::TYPE_BYTES => RustType::amp_slice_of_u8(), + Type::TYPE_ENUM => RustType::i32(), + t => t.rust_type(), + } + } + + /// Size of value for type, None if variable. + fn encoded_size(&self) -> Option<u32> { + match self { + Type::TYPE_BOOL => Some(1), + Type::TYPE_FIXED32 => Some(4), + Type::TYPE_FIXED64 => Some(8), + Type::TYPE_SFIXED32 => Some(4), + Type::TYPE_SFIXED64 => Some(8), + Type::TYPE_FLOAT => Some(4), + Type::TYPE_DOUBLE => Some(8), + _ => None, + } + } +} diff --git a/src/gen/file.rs b/src/gen/file.rs new file mode 100644 index 0000000..411b069 --- /dev/null +++ b/src/gen/file.rs @@ -0,0 +1,149 @@ +use std::collections::HashMap; + +use protobuf::descriptor::file_options; +use protobuf::descriptor::FileDescriptorProto; +use protobuf::reflect::FileDescriptor; +use protobuf_parse::ProtoPath; + +use crate::compiler_plugin; +use crate::customize::ctx::CustomizeElemCtx; +use crate::customize::rustproto_proto::customize_from_rustproto_for_file; +use crate::gen::code_writer::CodeWriter; +use crate::gen::enums::EnumGen; +use crate::gen::extensions::write_extensions; +use crate::gen::file_descriptor::write_file_descriptor_data; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::message::MessageGen; +use crate::gen::paths::proto_path_to_rust_mod; +use crate::gen::scope::FileScope; +use crate::gen::scope::RootScope; +use crate::proto_name_to_rs; + +pub(crate) struct GenFileResult { + pub(crate) compiler_plugin_result: compiler_plugin::GenResult, + pub(crate) mod_name: String, +} + +pub(crate) fn gen_file( + file_descriptor: &FileDescriptor, + _files_map: &HashMap<&ProtoPath, &FileDescriptor>, + root_scope: &RootScope, + parent_customize: &CustomizeElemCtx, + parser: &str, +) -> anyhow::Result<GenFileResult> { + let lite_runtime_from_builtin_option = file_descriptor + .proto() + .options + .get_or_default() + .optimize_for() + == file_options::OptimizeMode::LITE_RUNTIME; + + let mut customize_from_proto = + customize_from_rustproto_for_file(file_descriptor.proto().options.get_or_default()); + if customize_from_proto.lite_runtime.is_none() + && parent_customize.for_elem.lite_runtime.is_none() + { + customize_from_proto.lite_runtime = Some(lite_runtime_from_builtin_option); + } + + let customize = parent_customize.child(&customize_from_proto, file_descriptor); + + let file_scope = FileScope { file_descriptor }; + let scope = file_scope.to_scope(); + + let lite_runtime = customize.for_elem.lite_runtime.unwrap_or(false); + + let v = CodeWriter::with(|w| { + w.write_generated_by("rust-protobuf", "3.2.0", parser); + + w.write_line(""); + w.write_line(&format!( + "//! Generated file from `{}`", + file_descriptor.proto().name() + )); + + if customize.for_elem.lite_runtime.unwrap_or(false) { + w.comment("Generated for lite runtime"); + } + + if customize.for_elem.inside_protobuf != Some(true) { + w.write_line(""); + w.write_line("/// Generated files are compatible only with the same version"); + w.write_line("/// of protobuf runtime."); + w.write_line(&format!( + "const _PROTOBUF_VERSION_CHECK: () = {}::{};", + protobuf_crate_path(&customize.for_elem), + protobuf::VERSION_IDENT + )); + } + + static NESTED_TYPE_NUMBER: protobuf::rt::Lazy<i32> = protobuf::rt::Lazy::new(); + let message_type_number = *NESTED_TYPE_NUMBER.get(|| { + protobuf::reflect::MessageDescriptor::for_type::<FileDescriptorProto>() + .field_by_name("message_type") + .expect("`message_type` must exist") + .proto() + .number() + }); + + let mut path = vec![message_type_number, 0]; + for (id, message) in scope.messages().iter().enumerate() { + // ignore map entries, because they are not used in map fields + if !message.is_map() { + path[1] = id as i32; + + w.write_line(""); + MessageGen::new( + file_descriptor, + message, + &root_scope, + &customize, + &path, + file_descriptor.proto().source_code_info.as_ref(), + )? + .write(w)?; + } + } + + static ENUM_TYPE_NUMBER: protobuf::rt::Lazy<i32> = protobuf::rt::Lazy::new(); + let enum_type_number = *ENUM_TYPE_NUMBER.get(|| { + protobuf::reflect::MessageDescriptor::for_type::<FileDescriptorProto>() + .field_by_name("enum_type") + .expect("`enum_type` must exist") + .proto() + .number() + }); + + let mut path = vec![enum_type_number, 0]; + for (id, enum_type) in scope.enums().iter().enumerate() { + path[1] = id as i32; + + w.write_line(""); + EnumGen::new( + enum_type, + &customize, + root_scope, + &path, + file_descriptor.proto().source_code_info.as_ref(), + ) + .write(w); + } + + write_extensions(file_descriptor, &root_scope, w, &customize); + + if !lite_runtime { + w.write_line(""); + write_file_descriptor_data(file_descriptor, &customize.for_elem, w); + } + + Ok(()) + })?; + + Ok(GenFileResult { + compiler_plugin_result: compiler_plugin::GenResult { + name: proto_name_to_rs(file_descriptor.proto().name()), + content: v.into_bytes(), + }, + mod_name: proto_path_to_rust_mod(file_descriptor.proto().name()).into_string(), + }) +} diff --git a/src/gen/file_and_mod.rs b/src/gen/file_and_mod.rs new file mode 100644 index 0000000..8862c53 --- /dev/null +++ b/src/gen/file_and_mod.rs @@ -0,0 +1,8 @@ +use crate::customize::Customize; +use crate::gen::rust::rel_path::RustRelativePath; + +pub(crate) struct FileAndMod { + pub file: String, + pub relative_mod: RustRelativePath, + pub customize: Customize, +} diff --git a/src/gen/file_descriptor.rs b/src/gen/file_descriptor.rs new file mode 100644 index 0000000..2c8249c --- /dev/null +++ b/src/gen/file_descriptor.rs @@ -0,0 +1,208 @@ +use std::fmt::Write as _; + +use protobuf::reflect::FileDescriptor; +use protobuf::Message; + +use crate::gen::code_writer::CodeWriter; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::paths::proto_path_to_fn_file_descriptor; +use crate::gen::rust::snippets::expr_vec_with_capacity_const; +use crate::gen::scope::FileScope; +use crate::gen::scope::Scope; +use crate::gen::scope::WithScope; +use crate::Customize; + +fn escape_byte(s: &mut String, b: u8) { + if b == b'\n' { + write!(s, "\\n").unwrap(); + } else if b == b'\r' { + write!(s, "\\r").unwrap(); + } else if b == b'\t' { + write!(s, "\\t").unwrap(); + } else if b == b'\\' || b == b'"' { + write!(s, "\\{}", b as char).unwrap(); + } else if b == b'\0' { + write!(s, "\\0").unwrap(); + // ASCII printable except space + } else if b > 0x20 && b < 0x7f { + write!(s, "{}", b as char).unwrap(); + } else { + write!(s, "\\x{:02x}", b).unwrap(); + } +} + +fn write_generate_file_descriptor( + file_descriptor: &FileDescriptor, + customize: &Customize, + w: &mut CodeWriter, +) { + let deps = &file_descriptor.proto().dependency; + w.write_line(&format!( + "let mut deps = {vec_with_capacity};", + vec_with_capacity = expr_vec_with_capacity_const(deps.len()) + )); + for f in deps { + w.write_line(&format!( + "deps.push({}().clone());", + proto_path_to_fn_file_descriptor(f, customize) + )); + } + + let scope = FileScope { file_descriptor }; + + let messages = scope.find_messages_except_map(); + w.write_line(&format!( + "let mut messages = {vec_with_capacity};", + vec_with_capacity = expr_vec_with_capacity_const(messages.len()) + )); + for m in &messages { + w.write_line(&format!( + "messages.push({}::generated_message_descriptor_data());", + m.rust_name_to_file(), + )); + } + + let enums = scope.find_enums(); + w.write_line(&format!( + "let mut enums = {};", + expr_vec_with_capacity_const(enums.len()) + )); + for e in &enums { + w.write_line(&format!( + "enums.push({}::generated_enum_descriptor_data());", + e.rust_name_to_file(), + )); + } + + w.write_line(&format!( + "{}::reflect::GeneratedFileDescriptor::new_generated(", + protobuf_crate_path(&customize), + )); + w.indented(|w| { + w.write_line(&format!("file_descriptor_proto(),")); + w.write_line(&format!("deps,")); + w.write_line(&format!("messages,")); + w.write_line(&format!("enums,")); + }); + w.write_line(")"); +} + +fn write_file_descriptor( + file_descriptor: &FileDescriptor, + customize: &Customize, + w: &mut CodeWriter, +) { + w.write_line("/// `FileDescriptor` object which allows dynamic access to files"); + w.pub_fn( + &format!( + "file_descriptor() -> &'static {protobuf_crate}::reflect::FileDescriptor", + protobuf_crate = protobuf_crate_path(customize) + ), + |w| { + w.lazy_static( + "generated_file_descriptor_lazy", + &format!( + "{protobuf_crate}::reflect::GeneratedFileDescriptor", + protobuf_crate = protobuf_crate_path(customize) + ), + &format!("{}", protobuf_crate_path(customize)), + ); + w.lazy_static_decl_get( + "file_descriptor", + &format!( + "{protobuf_crate}::reflect::FileDescriptor", + protobuf_crate = protobuf_crate_path(customize) + ), + &format!("{}", protobuf_crate_path(customize)), + |w| { + w.block( + "let generated_file_descriptor = generated_file_descriptor_lazy.get(|| {", + "});", + |w| write_generate_file_descriptor(file_descriptor, customize, w), + ); + w.write_line(&format!( + "{protobuf_crate}::reflect::FileDescriptor::new_generated_2(generated_file_descriptor)", + protobuf_crate=protobuf_crate_path(&customize), + )); + } + ); + }, + ); +} + +pub(crate) fn write_file_descriptor_data( + file: &FileDescriptor, + customize: &Customize, + w: &mut CodeWriter, +) { + let fdp_bytes = file.proto().write_to_bytes().unwrap(); + w.write_line("static file_descriptor_proto_data: &'static [u8] = b\"\\"); + w.indented(|w| { + const MAX_LINE_LEN: usize = 72; + + let mut s = String::new(); + for &b in &fdp_bytes { + let prev_len = s.len(); + escape_byte(&mut s, b); + let truncate = s.len() > MAX_LINE_LEN; + if truncate { + s.truncate(prev_len); + } + if truncate || s.len() == MAX_LINE_LEN { + write!(s, "\\").unwrap(); + w.write_line(&s); + s.clear(); + } + if truncate { + escape_byte(&mut s, b); + } + } + if !s.is_empty() { + write!(s, "\\").unwrap(); + w.write_line(&s); + s.clear(); + } + }); + w.write_line("\";"); + w.write_line(""); + write_file_descriptor_proto(&customize, w); + w.write_line(""); + write_file_descriptor(file, &customize, w); +} + +fn write_file_descriptor_proto(customize: &Customize, w: &mut CodeWriter) { + w.write_line("/// `FileDescriptorProto` object which was a source for this generated file"); + w.def_fn( + &format!( + "file_descriptor_proto() -> &'static {protobuf_crate}::descriptor::FileDescriptorProto", + protobuf_crate=protobuf_crate_path(customize) + ), + |w| { + w.lazy_static_decl_get( + "file_descriptor_proto_lazy", + &format!( + "{protobuf_crate}::descriptor::FileDescriptorProto", + protobuf_crate=protobuf_crate_path(customize) + ), + &format!("{}", protobuf_crate_path(customize)), + |w| { + w.write_line(&format!( + "{protobuf_crate}::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()", + protobuf_crate=protobuf_crate_path(customize) + )); + }, + ); + }, + ); +} + +/// Code to generate call `module::file_descriptor()`. +pub(crate) fn file_descriptor_call_expr(scope: &Scope) -> String { + format!( + "{}()", + scope + .rust_path_to_file() + .to_reverse() + .append_ident("file_descriptor".into()) + ) +} diff --git a/src/gen/inside.rs b/src/gen/inside.rs new file mode 100644 index 0000000..d416031 --- /dev/null +++ b/src/gen/inside.rs @@ -0,0 +1,11 @@ +use crate::customize::Customize; +use crate::gen::rust::path::RustPath; + +/// Path to `protobuf` crate, different when `.proto` file is +/// used inside or outside of protobuf crate. +pub(crate) fn protobuf_crate_path(customize: &Customize) -> RustPath { + match customize.inside_protobuf { + Some(true) => RustPath::from("crate"), + _ => RustPath::from("::protobuf"), + } +} diff --git a/src/gen/map.rs b/src/gen/map.rs new file mode 100644 index 0000000..ba50eeb --- /dev/null +++ b/src/gen/map.rs @@ -0,0 +1,16 @@ +use crate::gen::scope::FieldWithContext; +use crate::gen::scope::MessageWithScope; + +/// Pair of (key, value) if this message is map entry +pub(crate) fn map_entry<'a>( + d: &'a MessageWithScope, +) -> Option<(FieldWithContext<'a>, FieldWithContext<'a>)> { + if d.message.is_map_entry() { + // `MessageDescriptor` validated the fields. + let key = d.fields()[0].clone(); + let value = d.fields()[1].clone(); + Some((key, value)) + } else { + None + } +} diff --git a/src/gen/message.rs b/src/gen/message.rs new file mode 100644 index 0000000..3471f87 --- /dev/null +++ b/src/gen/message.rs @@ -0,0 +1,786 @@ +use std::fmt; + +use protobuf::descriptor::*; +use protobuf::reflect::FileDescriptor; +use protobuf::reflect::MessageDescriptor; +use protobuf_parse::snake_case; + +use crate::customize::ctx::CustomizeElemCtx; +use crate::customize::ctx::SpecialFieldPseudoDescriptor; +use crate::customize::rustproto_proto::customize_from_rustproto_for_message; +use crate::gen::code_writer::*; +use crate::gen::descriptor::write_fn_descriptor; +use crate::gen::enums::*; +use crate::gen::field::FieldGen; +use crate::gen::field::FieldKind; +use crate::gen::file_and_mod::FileAndMod; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::oneof::OneofGen; +use crate::gen::oneof::OneofVariantGen; +use crate::gen::protoc_insertion_point::write_protoc_insertion_point_for_message; +use crate::gen::protoc_insertion_point::write_protoc_insertion_point_for_special_field; +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::ident_with_path::RustIdentWithPath; +use crate::gen::rust::rel_path::RustRelativePath; +use crate::gen::rust::snippets::expr_vec_with_capacity_const; +use crate::gen::rust::snippets::EXPR_NONE; +use crate::gen::rust_types_values::*; +use crate::gen::scope::MessageWithScope; +use crate::gen::scope::RootScope; +use crate::gen::scope::WithScope; +use crate::Customize; + +/// Protobuf message Rust type name +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct RustTypeMessage(pub RustIdentWithPath); + +impl fmt::Display for RustTypeMessage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl<S: Into<RustIdentWithPath>> From<S> for RustTypeMessage { + fn from(s: S) -> Self { + RustTypeMessage(s.into()) + } +} + +impl RustTypeMessage { + /// Code which emits default instance. + pub fn default_instance(&self, customize: &Customize) -> String { + format!( + "<{} as {}::Message>::default_instance()", + self.0, + protobuf_crate_path(customize) + ) + } +} + +/// Message info for codegen +pub(crate) struct MessageGen<'a> { + file_descriptor: &'a FileDescriptor, + message_descriptor: MessageDescriptor, + pub message: &'a MessageWithScope<'a>, + pub root_scope: &'a RootScope<'a>, + pub fields: Vec<FieldGen<'a>>, + pub lite_runtime: bool, + customize: CustomizeElemCtx<'a>, + path: &'a [i32], + info: Option<&'a SourceCodeInfo>, +} + +impl<'a> MessageGen<'a> { + pub fn new( + file_descriptor: &'a FileDescriptor, + message: &'a MessageWithScope<'a>, + root_scope: &'a RootScope<'a>, + parent_customize: &CustomizeElemCtx<'a>, + path: &'a [i32], + info: Option<&'a SourceCodeInfo>, + ) -> anyhow::Result<MessageGen<'a>> { + let message_descriptor = file_descriptor + .message_by_package_relative_name(&format!("{}", message.protobuf_name_to_package())) + .unwrap(); + + let customize = parent_customize.child( + &customize_from_rustproto_for_message(message.message.proto().options.get_or_default()), + &message.message, + ); + + static FIELD_NUMBER: protobuf::rt::Lazy<i32> = protobuf::rt::Lazy::new(); + let field_number = *FIELD_NUMBER.get(|| { + protobuf::reflect::MessageDescriptor::for_type::<DescriptorProto>() + .field_by_name("field") + .expect("`field` must exist") + .proto() + .number() + }); + + let fields: Vec<_> = message + .fields() + .into_iter() + .enumerate() + .map(|(id, field)| { + let mut path = path.to_vec(); + path.extend_from_slice(&[field_number, id as i32]); + FieldGen::parse(field, root_scope, &customize, path, info) + }) + .collect::<anyhow::Result<Vec<_>>>()?; + let lite_runtime = customize.for_elem.lite_runtime.unwrap_or_else(|| { + message.file_descriptor().proto().options.optimize_for() + == file_options::OptimizeMode::LITE_RUNTIME + }); + Ok(MessageGen { + message_descriptor, + file_descriptor, + message, + root_scope, + fields, + lite_runtime, + customize, + path, + info, + }) + } + + fn rust_name(&self) -> RustIdent { + self.message.rust_name() + } + + fn mod_name(&self) -> RustRelativePath { + self.message.scope.rust_path_to_file() + } + + pub fn file_and_mod(&self) -> FileAndMod { + self.message + .scope + .file_and_mod(self.customize.for_elem.clone()) + } + + fn oneofs(&'a self) -> Vec<OneofGen<'a>> { + self.message + .oneofs() + .into_iter() + .map(|oneof| OneofGen::parse(self, oneof, &self.customize)) + .collect() + } + + fn required_fields(&'a self) -> Vec<&'a FieldGen> { + self.fields + .iter() + .filter(|f| match f.kind { + FieldKind::Singular(ref singular) => singular.flag.is_required(), + _ => false, + }) + .collect() + } + + fn message_fields(&'a self) -> Vec<&'a FieldGen> { + self.fields + .iter() + .filter(|f| f.proto_type == field_descriptor_proto::Type::TYPE_MESSAGE) + .collect() + } + + fn fields_except_oneof(&'a self) -> Vec<&'a FieldGen> { + self.fields + .iter() + .filter(|f| match f.kind { + FieldKind::Oneof(..) => false, + _ => true, + }) + .collect() + } + + fn fields_except_group(&'a self) -> Vec<&'a FieldGen> { + self.fields + .iter() + .filter(|f| f.proto_type != field_descriptor_proto::Type::TYPE_GROUP) + .collect() + } + + fn fields_except_oneof_and_group(&'a self) -> Vec<&'a FieldGen> { + self.fields + .iter() + .filter(|f| match f.kind { + FieldKind::Oneof(..) => false, + _ => f.proto_type != field_descriptor_proto::Type::TYPE_GROUP, + }) + .collect() + } + + fn write_match_each_oneof_variant<F>(&self, w: &mut CodeWriter, cb: F) + where + F: Fn(&mut CodeWriter, &OneofVariantGen, &RustValueTyped), + { + for oneof in self.oneofs() { + let variants = oneof.variants_except_group(); + if variants.is_empty() { + // Special case because + // https://github.com/rust-lang/rust/issues/50642 + continue; + } + w.if_let_stmt( + "::std::option::Option::Some(ref v)", + &format!("self.{}", oneof.oneof.field_name())[..], + |w| { + w.match_block("v", |w| { + for variant in variants { + let ref field = variant.field; + + let (refv, vtype) = if field.elem_type_is_copy() { + ("v", variant.rust_type(&self.file_and_mod())) + } else { + ("ref v", variant.rust_type(&self.file_and_mod()).ref_type()) + }; + w.case_block( + format!("&{}({})", variant.path(&self.file_and_mod()), refv), + |w| { + cb( + w, + &variant, + &RustValueTyped { + value: "v".to_owned(), + rust_type: vtype.clone(), + }, + ); + }, + ); + } + }); + }, + ); + } + } + + fn write_write_to_with_cached_sizes(&self, w: &mut CodeWriter) { + let sig = format!( + "write_to_with_cached_sizes(&self, os: &mut {protobuf_crate}::CodedOutputStream<'_>) -> {protobuf_crate}::Result<()>", + protobuf_crate=protobuf_crate_path(&self.customize.for_elem), + ); + w.def_fn(&sig, |w| { + // To have access to its methods but not polute the name space. + for f in self.fields_except_oneof_and_group() { + f.write_message_write_field("os", w); + } + self.write_match_each_oneof_variant(w, |w, variant, v| { + variant + .field + .write_write_element(variant.elem(), w, "os", v); + }); + w.write_line("os.write_unknown_fields(self.special_fields.unknown_fields())?;"); + w.write_line("::std::result::Result::Ok(())"); + }); + } + + fn write_default_instance_lazy(&self, w: &mut CodeWriter) { + w.lazy_static_decl_get_simple( + "instance", + &format!("{}", self.rust_name()), + &format!("{}::new", self.rust_name()), + &format!("{}", protobuf_crate_path(&self.customize.for_elem)), + ); + } + + fn write_default_instance_static(&self, w: &mut CodeWriter) { + w.stmt_block( + &format!( + "static instance: {} = {}", + self.rust_name(), + self.rust_name() + ), + |w| { + for f in &self.fields_except_oneof_and_group() { + w.field_entry( + &f.rust_name.to_string(), + &f.kind + .default(&self.customize.for_elem, &self.file_and_mod(), true), + ); + } + for o in &self.oneofs() { + w.field_entry(&o.oneof.field_name().to_string(), EXPR_NONE); + } + w.field_entry( + "special_fields", + &format!( + "{}::SpecialFields::new()", + protobuf_crate_path(&self.customize.for_elem) + ), + ); + }, + ); + w.write_line("&instance"); + } + + fn write_default_instance(&self, w: &mut CodeWriter) { + w.def_fn( + &format!("default_instance() -> &'static {}", self.rust_name()), + |w| { + let has_map_field = self.fields.iter().any(|f| match f.kind { + FieldKind::Map(..) => true, + _ => false, + }); + if has_map_field { + self.write_default_instance_lazy(w) + } else { + self.write_default_instance_static(w) + } + }, + ); + } + + fn write_compute_size(&self, w: &mut CodeWriter) { + // Append sizes of messages in the tree to the specified vector. + // First appended element is size of self, and then nested message sizes. + // in serialization order are appended recursively."); + w.comment("Compute sizes of nested messages"); + // there are unused variables in oneof + w.allow(&["unused_variables"]); + w.def_fn("compute_size(&self) -> u64", |w| { + // To have access to its methods but not polute the name space. + w.write_line("let mut my_size = 0;"); + for field in self.fields_except_oneof_and_group() { + field.write_message_compute_field_size("my_size", w); + } + self.write_match_each_oneof_variant(w, |w, variant, v| { + variant + .field + .write_element_size(variant.elem(), w, v, "my_size"); + }); + w.write_line(&format!( + "my_size += {}::rt::unknown_fields_size(self.special_fields.unknown_fields());", + protobuf_crate_path(&self.customize.for_elem) + )); + w.write_line("self.special_fields.cached_size().set(my_size as u32);"); + w.write_line("my_size"); + }); + } + + fn write_field_accessors(&self, w: &mut CodeWriter) { + for f in self.fields_except_group() { + f.write_message_single_field_accessors(w); + } + } + + fn write_impl_self(&self, w: &mut CodeWriter) { + w.impl_self_block(&format!("{}", self.rust_name()), |w| { + w.pub_fn(&format!("new() -> {}", self.rust_name()), |w| { + w.write_line("::std::default::Default::default()"); + }); + + self.write_field_accessors(w); + + if !self.lite_runtime { + w.write_line(""); + self.write_generated_message_descriptor_data(w); + } + }); + } + + fn write_unknown_fields(&self, w: &mut CodeWriter) { + let sig = format!( + "special_fields(&self) -> &{}::SpecialFields", + protobuf_crate_path(&self.customize.for_elem) + ); + w.def_fn(&sig, |w| { + w.write_line("&self.special_fields"); + }); + w.write_line(""); + let sig = format!( + "mut_special_fields(&mut self) -> &mut {}::SpecialFields", + protobuf_crate_path(&self.customize.for_elem) + ); + w.def_fn(&sig, |w| { + w.write_line("&mut self.special_fields"); + }); + } + + fn write_merge_from(&self, w: &mut CodeWriter) { + let sig = format!( + "merge_from(&mut self, is: &mut {}::CodedInputStream<'_>) -> {}::Result<()>", + protobuf_crate_path(&self.customize.for_elem), + protobuf_crate_path(&self.customize.for_elem), + ); + w.def_fn(&sig, |w| { + w.while_block("let Some(tag) = is.read_raw_tag_or_eof()?", |w| { + w.match_block("tag", |w| { + for f in &self.fields_except_group() { + f.write_merge_from_field_case_block(w); + } + w.case_block("tag", |w| { + w.write_line(&format!("{}::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;", protobuf_crate_path(&self.customize.for_elem))); + }); + }); + }); + w.write_line("::std::result::Result::Ok(())"); + }); + } + + fn write_impl_message_full_fn_descriptor(&self, w: &mut CodeWriter) { + write_fn_descriptor( + &self.message.message, + self.message.scope(), + &self.customize.for_elem, + w, + ); + } + + fn write_generated_message_descriptor_data(&self, w: &mut CodeWriter) { + let sig = format!( + "generated_message_descriptor_data() -> {}::reflect::GeneratedMessageDescriptorData", + protobuf_crate_path(&self.customize.for_elem) + ); + w.fn_block( + Visibility::Path(self.message.scope().rust_path_to_file().to_reverse()), + &sig, + |w| { + let fields = self.fields_except_group(); + let oneofs = self.oneofs(); + w.write_line(&format!( + "let mut fields = {};", + expr_vec_with_capacity_const(fields.len()) + )); + w.write_line(&format!( + "let mut oneofs = {};", + expr_vec_with_capacity_const(oneofs.len()) + )); + for field in fields { + field.write_push_accessor("fields", w); + } + for oneof in oneofs { + w.write_line(&format!( + "oneofs.push({}::generated_oneof_descriptor_data());", + oneof.type_name_relative(&self.mod_name()) + )); + } + w.write_line(&format!( + "{}::reflect::GeneratedMessageDescriptorData::new_2::<{}>(", + protobuf_crate_path(&self.customize.for_elem), + self.rust_name(), + )); + w.indented(|w| { + w.write_line(&format!("\"{}\",", self.message.name_to_package())); + w.write_line("fields,"); + w.write_line("oneofs,"); + }); + w.write_line(")"); + }, + ); + } + + fn write_is_initialized(&self, w: &mut CodeWriter) { + w.def_fn(&format!("is_initialized(&self) -> bool"), |w| { + if !self.message.message.is_initialized_is_always_true() { + // TODO: use single loop + + for f in self.required_fields() { + f.write_if_self_field_is_none(w, |w| { + w.write_line("return false;"); + }); + } + + for f in self.message_fields() { + if let FieldKind::Map(..) = f.kind { + // TODO + w.comment("TODO: check map values are initialized"); + continue; + } + + f.write_for_self_field(w, "v", |w, _t| { + w.if_stmt("!v.is_initialized()", |w| { + w.write_line("return false;"); + }); + }); + } + } + w.write_line("true"); + }); + } + + fn write_impl_message(&self, w: &mut CodeWriter) { + w.impl_for_block( + &format!("{}::Message", protobuf_crate_path(&self.customize.for_elem),), + &format!("{}", self.rust_name()), + |w| { + w.write_line(&format!( + "const NAME: &'static str = \"{}\";", + self.message.message.name() + )); + w.write_line(""); + self.write_is_initialized(w); + w.write_line(""); + self.write_merge_from(w); + w.write_line(""); + self.write_compute_size(w); + w.write_line(""); + self.write_write_to_with_cached_sizes(w); + w.write_line(""); + self.write_unknown_fields(w); + w.write_line(""); + w.def_fn(&format!("new() -> {}", self.rust_name()), |w| { + w.write_line(&format!("{}::new()", self.rust_name())); + }); + w.write_line(""); + w.def_fn("clear(&mut self)", |w| { + for f in self.fields_except_group() { + f.write_clear(w); + } + w.write_line("self.special_fields.clear();"); + }); + w.write_line(""); + self.write_default_instance(w); + }, + ); + } + + fn write_impl_message_full(&self, w: &mut CodeWriter) { + w.impl_for_block( + &format!( + "{}::MessageFull", + protobuf_crate_path(&self.customize.for_elem), + ), + &format!("{}", self.rust_name()), + |w| { + self.write_impl_message_full_fn_descriptor(w); + }, + ); + } + + fn write_impl_value(&self, w: &mut CodeWriter) { + w.impl_for_block( + &format!( + "{}::reflect::ProtobufValue", + protobuf_crate_path(&self.customize.for_elem) + ), + &format!("{}", self.rust_name()), + |w| { + w.write_line(&format!( + "type RuntimeType = {}::reflect::rt::RuntimeTypeMessage<Self>;", + protobuf_crate_path(&self.customize.for_elem) + )); + }, + ) + } + + fn write_impl_display(&self, w: &mut CodeWriter) { + w.impl_for_block( + "::std::fmt::Display", + &format!("{}", self.rust_name()), + |w| { + w.def_fn( + "fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result", + |w| { + w.write_line(&format!( + "{}::text_format::fmt(self, f)", + protobuf_crate_path(&self.customize.for_elem) + )); + }, + ); + }, + ); + } + + fn supports_derive_partial_eq(&self) -> bool { + // There's stack overflow in the compiler when struct has too many fields + // https://github.com/rust-lang/rust/issues/40119 + self.fields.len() <= 500 + } + + fn write_struct(&self, w: &mut CodeWriter) { + let mut derive = Vec::new(); + if self.supports_derive_partial_eq() { + derive.push("PartialEq"); + } + derive.extend(&["Clone", "Default", "Debug"]); + w.derive(&derive); + write_protoc_insertion_point_for_message( + w, + &self.customize.for_elem, + &self.message_descriptor, + ); + w.pub_struct(&format!("{}", self.rust_name()), |w| { + if !self.fields_except_oneof().is_empty() { + w.comment("message fields"); + for field in self.fields_except_oneof() { + field.write_struct_field(w); + } + } + if !self.oneofs().is_empty() { + w.comment("message oneof groups"); + for oneof in self.oneofs() { + w.field_decl_vis( + Visibility::Public, + &oneof.oneof.field_name().to_string(), + &oneof.full_storage_type().to_code(&self.customize.for_elem), + ); + } + } + w.comment("special fields"); + + let customize_special_fields = self + .customize + .child( + &Customize::default(), + &SpecialFieldPseudoDescriptor { + message: &self.message.message, + field: "special_fields", + }, + ) + .for_elem; + + write_protoc_insertion_point_for_special_field( + w, + &customize_special_fields, + &self.message_descriptor, + "special_fields", + ); + w.pub_field_decl( + "special_fields", + &format!( + "{}::SpecialFields", + protobuf_crate_path(&self.customize.for_elem) + ), + ); + }); + } + + fn write_impl_default_for_amp(&self, w: &mut CodeWriter) { + w.impl_args_for_block( + &["'a"], + "::std::default::Default", + &format!("&'a {}", self.rust_name()), + |w| { + w.def_fn(&format!("default() -> &'a {}", self.rust_name()), |w| { + w.write_line(&format!( + "<{} as {}::Message>::default_instance()", + self.rust_name(), + protobuf_crate_path(&self.customize.for_elem), + )); + }); + }, + ); + } + + fn write_dummy_impl_partial_eq(&self, w: &mut CodeWriter) { + w.impl_for_block( + "::std::cmp::PartialEq", + &format!("{}", self.rust_name()), + |w| { + w.def_fn("eq(&self, _: &Self) -> bool", |w| { + w.comment("https://github.com/rust-lang/rust/issues/40119"); + w.unimplemented(); + }); + }, + ); + } + + pub fn write(&self, w: &mut CodeWriter) -> anyhow::Result<()> { + w.all_documentation(self.info, self.path); + self.write_struct(w); + + w.write_line(""); + self.write_impl_default_for_amp(w); + + if !self.supports_derive_partial_eq() { + w.write_line(""); + self.write_dummy_impl_partial_eq(w); + } + + w.write_line(""); + self.write_impl_self(w); + w.write_line(""); + self.write_impl_message(w); + if !self.lite_runtime { + w.write_line(""); + self.write_impl_message_full(w); + } + if !self.lite_runtime { + w.write_line(""); + self.write_impl_display(w); + + w.write_line(""); + self.write_impl_value(w); + } + + let mod_name = message_name_to_nested_mod_name(&self.message.message.name()); + + let oneofs = self.oneofs(); + let nested_messages: Vec<_> = self + .message + .to_scope() + .messages() + .into_iter() + .filter(|nested| { + // ignore map entries, because they are not used in map fields + !nested.is_map() + }) + .collect(); + let nested_enums = self.message.to_scope().enums(); + + if !oneofs.is_empty() || !nested_messages.is_empty() || !nested_enums.is_empty() { + w.write_line(""); + w.write_line(&format!( + "/// Nested message and enums of message `{}`", + self.message.message.name() + )); + w.pub_mod(&mod_name.to_string(), |w| { + let mut first = true; + + for oneof in &oneofs { + w.write_line(""); + oneof.write(w); + } + + static NESTED_TYPE_NUMBER: protobuf::rt::Lazy<i32> = protobuf::rt::Lazy::new(); + let nested_type_number = *NESTED_TYPE_NUMBER.get(|| { + MessageDescriptor::for_type::<DescriptorProto>() + .field_by_name("nested_type") + .expect("`nested_type` must exist") + .proto() + .number() + }); + + let mut path = self.path.to_vec(); + path.extend(&[nested_type_number, 0]); + for (id, nested) in nested_messages.iter().enumerate() { + let len = path.len() - 1; + path[len] = id as i32; + + if !first { + w.write_line(""); + } + first = false; + MessageGen::new( + &self.file_descriptor, + nested, + self.root_scope, + &self.customize, + &path, + self.info, + ) + // TODO: do not unwrap. + .unwrap() + .write(w) + // TODO: do not unwrap. + .unwrap(); + } + + static ENUM_TYPE_NUMBER: protobuf::rt::Lazy<i32> = protobuf::rt::Lazy::new(); + let enum_type_number = *ENUM_TYPE_NUMBER.get(|| { + MessageDescriptor::for_type::<DescriptorProto>() + .field_by_name("enum_type") + .expect("`enum_type` must exist") + .proto() + .number() + }); + + let len = path.len() - 2; + path[len] = enum_type_number; + for (id, enum_type) in self.message.to_scope().enums().iter().enumerate() { + let len = path.len() - 1; + path[len] = id as i32; + + if !first { + w.write_line(""); + } + first = false; + EnumGen::new( + enum_type, + &self.customize, + self.root_scope, + &path, + self.info, + ) + .write(w); + } + }); + } + Ok(()) + } +} + +pub(crate) fn message_name_to_nested_mod_name(message_name: &str) -> RustIdent { + let mod_name = snake_case(message_name); + RustIdent::new(&mod_name) +} diff --git a/src/gen/mod.rs b/src/gen/mod.rs new file mode 100644 index 0000000..6d0435c --- /dev/null +++ b/src/gen/mod.rs @@ -0,0 +1,21 @@ +pub(crate) mod all; +pub(crate) mod code_writer; +pub(crate) mod descriptor; +pub(crate) mod enums; +pub(crate) mod extensions; +pub(crate) mod field; +pub(crate) mod file; +pub(crate) mod file_and_mod; +pub(crate) mod file_descriptor; +pub(crate) mod inside; +mod map; +pub(crate) mod message; +pub(crate) mod mod_rs; +pub(crate) mod oneof; +pub(crate) mod paths; +pub(crate) mod protoc_insertion_point; +pub(crate) mod rust; +pub(crate) mod rust_types_values; +pub(crate) mod scope; +pub(crate) mod strx; +pub(crate) mod well_known_types; diff --git a/src/gen/mod_rs.rs b/src/gen/mod_rs.rs new file mode 100644 index 0000000..a149319 --- /dev/null +++ b/src/gen/mod_rs.rs @@ -0,0 +1,18 @@ +use crate::compiler_plugin; +use crate::gen::code_writer::CodeWriter; + +pub(crate) fn gen_mod_rs(mods: &[String]) -> compiler_plugin::GenResult { + let v = CodeWriter::with_no_error(|w| { + w.comment(&format!("{}generated", "@")); + w.write_line(""); + let mut mods: Vec<&String> = mods.into_iter().collect(); + mods.sort(); + for m in mods { + w.write_line(&format!("pub mod {};", m)); + } + }); + compiler_plugin::GenResult { + name: "mod.rs".to_owned(), + content: v.into_bytes(), + } +} diff --git a/src/gen/oneof.rs b/src/gen/oneof.rs new file mode 100644 index 0000000..3f34cdc --- /dev/null +++ b/src/gen/oneof.rs @@ -0,0 +1,373 @@ +//! Oneof-related codegen functions. + +use std::collections::HashSet; + +use protobuf::descriptor::field_descriptor_proto; +use protobuf::descriptor::file_options; +use protobuf::reflect::FieldDescriptor; +use protobuf_parse::ProtobufAbsPath; + +use crate::customize::ctx::CustomizeElemCtx; +use crate::customize::Customize; +use crate::gen::code_writer::CodeWriter; +use crate::gen::code_writer::Visibility; +use crate::gen::field::elem::FieldElem; +use crate::gen::field::rust_variant_name_for_protobuf_oneof_field_name; +use crate::gen::field::FieldGen; +use crate::gen::file_and_mod::FileAndMod; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::message::MessageGen; +use crate::gen::protoc_insertion_point::write_protoc_insertion_point_for_oneof; +use crate::gen::protoc_insertion_point::write_protoc_insertion_point_for_oneof_field; +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::ident_with_path::RustIdentWithPath; +use crate::gen::rust::path::RustPath; +use crate::gen::rust::rel_path::RustRelativePath; +use crate::gen::rust_types_values::make_path; +use crate::gen::rust_types_values::RustType; +use crate::gen::scope::OneofVariantWithContext; +use crate::gen::scope::OneofWithContext; +use crate::gen::scope::RootScope; +use crate::gen::scope::WithScope; + +// oneof one { ... } +#[derive(Clone)] +pub(crate) struct OneofField<'a> { + pub elem: FieldElem<'a>, + pub oneof_variant_rust_name: RustIdent, + pub oneof_field_name: RustIdent, + pub type_name: RustIdentWithPath, + pub boxed: bool, +} + +impl<'a> OneofField<'a> { + // Detecting recursion: if oneof fields contains a self-reference + // or another message which has a reference to self, + // put oneof variant into a box. + fn need_boxed( + field: &FieldDescriptor, + root_scope: &RootScope, + owner_name: &ProtobufAbsPath, + ) -> bool { + let mut visited_messages = HashSet::new(); + let mut fields = vec![field.clone()]; + while let Some(field) = fields.pop() { + if field.proto().type_() == field_descriptor_proto::Type::TYPE_MESSAGE { + let message_name = ProtobufAbsPath::from(field.proto().type_name()); + if !visited_messages.insert(message_name.clone()) { + continue; + } + if message_name == *owner_name { + return true; + } + let message = root_scope.find_message(&message_name); + fields.extend( + message + .message + .fields() + .into_iter() + .filter(|f| f.containing_oneof().is_some()), + ); + } + } + false + } + + pub fn parse( + oneof: &OneofWithContext<'a>, + field: &FieldDescriptor, + elem: FieldElem<'a>, + root_scope: &RootScope, + ) -> OneofField<'a> { + let boxed = OneofField::need_boxed(field, root_scope, &oneof.message.name_absolute()); + + OneofField { + elem, + type_name: oneof.rust_name(), + boxed, + oneof_variant_rust_name: rust_variant_name_for_protobuf_oneof_field_name(field.name()), + oneof_field_name: oneof.field_name(), + } + } + + pub fn rust_type(&self, reference: &FileAndMod) -> RustType { + let t = self.elem.rust_storage_elem_type(reference); + + if self.boxed { + RustType::Uniq(Box::new(t)) + } else { + t + } + } + + pub fn variant_path(&self, reference: &RustRelativePath) -> RustIdentWithPath { + make_path( + reference, + &self + .type_name + .to_path() + .with_ident(self.oneof_variant_rust_name.clone()), + ) + } +} + +#[derive(Clone)] +pub(crate) struct OneofVariantGen<'a> { + oneof: &'a OneofGen<'a>, + _variant: OneofVariantWithContext<'a>, + oneof_field: OneofField<'a>, + pub field: FieldGen<'a>, + _path: String, +} + +impl<'a> OneofVariantGen<'a> { + fn parse( + oneof: &'a OneofGen<'a>, + variant: OneofVariantWithContext<'a>, + field: &'a FieldGen, + _root_scope: &RootScope, + ) -> OneofVariantGen<'a> { + OneofVariantGen { + oneof, + _variant: variant.clone(), + field: field.clone(), + _path: format!( + "{}::{}", + oneof.type_name_relative(&oneof.oneof.message.scope.rust_path_to_file()), + field.rust_name + ), + oneof_field: OneofField::parse( + variant.oneof, + &field.proto_field.field, + field.elem().clone(), + oneof.message.root_scope, + ), + } + } + + pub fn rust_type(&self, reference: &FileAndMod) -> RustType { + self.oneof_field.rust_type(reference) + } + + pub fn path(&self, reference: &FileAndMod) -> RustPath { + RustPath::from(format!( + "{}::{}", + self.oneof.type_name_relative(&reference.relative_mod), + self.oneof_field.oneof_variant_rust_name, + )) + } + + pub(crate) fn elem(&self) -> &FieldElem<'_> { + self.field.elem() + } +} + +pub(crate) struct OneofGen<'a> { + // Message containing this oneof + message: &'a MessageGen<'a>, + pub oneof: OneofWithContext<'a>, + customize: CustomizeElemCtx<'a>, + lite_runtime: bool, +} + +impl<'a> OneofGen<'a> { + pub fn parse( + message: &'a MessageGen, + oneof: OneofWithContext<'a>, + parent_customize: &CustomizeElemCtx<'a>, + ) -> OneofGen<'a> { + let customize = parent_customize.child(&Customize::default(), &oneof.oneof); + let lite_runtime = customize.for_elem.lite_runtime.unwrap_or_else(|| { + oneof + .message + .file_descriptor() + .proto() + .options + .optimize_for() + == file_options::OptimizeMode::LITE_RUNTIME + }); + OneofGen { + message, + oneof, + customize, + lite_runtime, + } + } + + pub fn type_name_relative(&self, source: &RustRelativePath) -> RustIdentWithPath { + make_path(source, &self.oneof.rust_name()) + } + + pub fn variants_except_group(&'a self) -> Vec<OneofVariantGen<'a>> { + self.oneof + .variants() + .into_iter() + .filter_map(|v| { + let field = self + .message + .fields + .iter() + .filter(|f| f.proto_field.name() == v.field.name()) + .next() + .expect(&format!("field not found by name: {}", v.field.name())); + match field.proto_type { + field_descriptor_proto::Type::TYPE_GROUP => None, + _ => Some(OneofVariantGen::parse( + self, + v, + field, + self.message.root_scope, + )), + } + }) + .collect() + } + + pub fn full_storage_type(&self) -> RustType { + RustType::Option(Box::new(RustType::Oneof( + self.type_name_relative( + &self + .oneof + .message + .scope + .file_and_mod(self.customize.for_elem.clone()) + .relative_mod, + ) + .clone(), + ))) + } + + fn file_and_mod(&self) -> FileAndMod { + let mut file_and_mod = self + .message + .message + .scope + .file_and_mod(self.customize.for_elem.clone()); + file_and_mod + .relative_mod + .push_ident(self.message.message.mod_name()); + file_and_mod + } + + fn write_enum(&self, w: &mut CodeWriter) { + let derive = vec!["Clone", "PartialEq", "Debug"]; + w.derive(&derive); + w.write_line("#[non_exhaustive]"); + write_protoc_insertion_point_for_oneof(w, &self.customize.for_elem, &self.oneof.oneof); + w.pub_enum(&self.oneof.rust_name().ident.to_string(), |w| { + for variant in self.variants_except_group() { + write_protoc_insertion_point_for_oneof_field( + w, + &self.customize.for_children, + &variant.field.proto_field.field, + ); + w.write_line(&format!( + "{}({}),", + variant.oneof_field.oneof_variant_rust_name, + &variant + .rust_type(&self.file_and_mod()) + .to_code(&self.customize.for_elem) + )); + } + }); + } + + fn write_impl_oneof(&self, w: &mut CodeWriter) { + w.impl_for_block( + &format!("{}::Oneof", protobuf_crate_path(&self.customize.for_elem)), + self.oneof.rust_name().ident.to_string(), + |_w| { + // nothing here yet + }, + ); + } + + fn write_impl_oneof_full_fn_descriptor(&self, w: &mut CodeWriter) { + let sig = format!( + "descriptor() -> {}::reflect::OneofDescriptor", + protobuf_crate_path(&self.customize.for_elem), + ); + w.def_fn(&sig, |w| { + w.lazy_static( + "descriptor", + &format!( + "{}::reflect::OneofDescriptor", + protobuf_crate_path(&self.customize.for_elem), + ), + &protobuf_crate_path(&self.customize.for_elem).to_string(), + ); + let message_type = make_path( + &self + .oneof + .message + .scope() + .rust_path_to_file() + .append(self.oneof.message.mod_name().into_rel_path()), + &self.oneof.message.rust_name_to_file(), + ); + let expr = format!( + "<{} as {}::MessageFull>::descriptor().oneof_by_name(\"{}\").unwrap()", + message_type, + protobuf_crate_path(&self.customize.for_elem), + self.oneof.oneof.name() + ); + w.write_line(&format!("descriptor.get(|| {}).clone()", expr)); + }); + } + + fn write_impl_oneof_full(&self, w: &mut CodeWriter) { + w.impl_for_block( + &format!( + "{}::OneofFull", + protobuf_crate_path(&self.customize.for_elem) + ), + self.oneof.rust_name().ident.to_string(), + |w| self.write_impl_oneof_full_fn_descriptor(w), + ) + } + + fn write_generated_oneof_descriptor_data(&self, w: &mut CodeWriter) { + let sig = format!( + "generated_oneof_descriptor_data() -> {}::reflect::GeneratedOneofDescriptorData", + protobuf_crate_path(&self.customize.for_elem) + ); + w.fn_block( + Visibility::Path( + self.oneof + .rust_name() + .path + .into_relative_or_panic() + .to_reverse(), + ), + &sig, + |w| { + w.write_line(&format!( + "{}::reflect::GeneratedOneofDescriptorData::new::<{}>(\"{}\")", + protobuf_crate_path(&self.customize.for_elem), + &self.oneof.rust_name().ident, + self.oneof.oneof.name(), + )); + }, + ); + } + + fn write_impl_self(&self, w: &mut CodeWriter) { + w.impl_self_block(&format!("{}", &self.oneof.rust_name().ident), |w| { + if !self.lite_runtime { + self.write_generated_oneof_descriptor_data(w); + } + }); + } + + pub fn write(&self, w: &mut CodeWriter) { + self.write_enum(w); + w.write_line(""); + self.write_impl_oneof(w); + if !self.lite_runtime { + w.write_line(""); + self.write_impl_oneof_full(w); + } + w.write_line(""); + self.write_impl_self(w); + } +} diff --git a/src/gen/paths.rs b/src/gen/paths.rs new file mode 100644 index 0000000..2bc958d --- /dev/null +++ b/src/gen/paths.rs @@ -0,0 +1,109 @@ +use crate::gen::inside::protobuf_crate_path; +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::path::RustPath; +use crate::gen::strx; +use crate::gen::well_known_types::WELL_KNOWN_TYPES_PROTO_FILE_FULL_NAMES; +use crate::Customize; + +// Copy-pasted from libsyntax. +fn ident_start(c: char) -> bool { + (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' +} + +// Copy-pasted from libsyntax. +fn ident_continue(c: char) -> bool { + (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' +} + +pub(crate) fn proto_path_to_rust_mod(path: &str) -> RustIdent { + let without_dir = strx::remove_to(path, std::path::is_separator); + let without_suffix = strx::remove_suffix(without_dir, ".proto"); + + let name = without_suffix + .chars() + .enumerate() + .map(|(i, c)| { + let valid = if i == 0 { + ident_start(c) + } else { + ident_continue(c) + }; + if valid { + c + } else { + '_' + } + }) + .collect::<String>(); + + RustIdent::new(&name) +} + +/// Used in protobuf-codegen-identical-test +pub fn proto_name_to_rs(proto_file_path: &str) -> String { + format!("{}.rs", proto_path_to_rust_mod(proto_file_path)) +} + +pub(crate) fn proto_path_to_fn_file_descriptor( + proto_path: &str, + customize: &Customize, +) -> RustPath { + let protobuf_crate = protobuf_crate_path(customize); + match proto_path { + "rustproto.proto" => protobuf_crate.append("rustproto::file_descriptor".into()), + "google/protobuf/descriptor.proto" => { + protobuf_crate.append("descriptor::file_descriptor".into()) + } + s if WELL_KNOWN_TYPES_PROTO_FILE_FULL_NAMES.contains(&s) => protobuf_crate + .append_ident("well_known_types".into()) + .append_ident(proto_path_to_rust_mod(s)) + .append_ident("file_descriptor".into()), + s => RustPath::super_path() + .append_ident(proto_path_to_rust_mod(s)) + .append_ident("file_descriptor".into()), + } +} + +#[cfg(test)] +mod test { + use super::proto_path_to_rust_mod; + use crate::gen::rust::ident::RustIdent; + + #[test] + fn test_mod_path_proto_ext() { + assert_eq!( + RustIdent::from("proto"), + proto_path_to_rust_mod("proto.proto") + ); + } + + #[test] + fn test_mod_path_unknown_ext() { + assert_eq!( + RustIdent::from("proto_proto3"), + proto_path_to_rust_mod("proto.proto3") + ); + } + + #[test] + fn test_mod_path_empty_ext() { + assert_eq!(RustIdent::from("proto"), proto_path_to_rust_mod("proto")); + } + + #[test] + fn test_mod_path_dir() { + assert_eq!( + RustIdent::from("baz"), + proto_path_to_rust_mod("foo/bar/baz.proto"), + ) + } + + #[cfg(target_os = "windows")] + #[test] + fn test_mod_path_dir_backslashes() { + assert_eq!( + RustIdent::from("baz"), + proto_path_to_rust_mod("foo\\bar\\baz.proto"), + ) + } +} diff --git a/src/gen/protoc_insertion_point.rs b/src/gen/protoc_insertion_point.rs new file mode 100644 index 0000000..0083215 --- /dev/null +++ b/src/gen/protoc_insertion_point.rs @@ -0,0 +1,80 @@ +use protobuf::reflect::EnumDescriptor; +use protobuf::reflect::EnumValueDescriptor; +use protobuf::reflect::FieldDescriptor; +use protobuf::reflect::MessageDescriptor; +use protobuf::reflect::OneofDescriptor; + +use crate::gen::code_writer::CodeWriter; +use crate::Customize; + +/// Write `// @protoc_insertion_point(...)` before the element. +/// +/// This is similar to what `protoc` codegen does for C++ or Java. +/// This can be used to modify the generated code. +fn write_protoc_insertion_point(w: &mut CodeWriter, customize: &Customize, arg: &str) { + for line in customize.before.iter().flat_map(|s| s.lines()) { + w.write_line(line); + } + w.comment(&format!("@@protoc_insertion_point({})", arg)); +} + +pub(crate) fn write_protoc_insertion_point_for_message( + w: &mut CodeWriter, + customize: &Customize, + message: &MessageDescriptor, +) { + write_protoc_insertion_point(w, customize, &format!("message:{}", message.full_name())); +} + +pub(crate) fn write_protoc_insertion_point_for_field( + w: &mut CodeWriter, + customize: &Customize, + field: &FieldDescriptor, +) { + write_protoc_insertion_point(w, customize, &format!("field:{}", field.full_name())); +} + +pub(crate) fn write_protoc_insertion_point_for_special_field( + w: &mut CodeWriter, + customize: &Customize, + message: &MessageDescriptor, + field: &str, +) { + write_protoc_insertion_point( + w, + customize, + &format!("special_field:{}.{}", message.full_name(), field), + ); +} + +pub(crate) fn write_protoc_insertion_point_for_enum( + w: &mut CodeWriter, + customize: &Customize, + enumeration: &EnumDescriptor, +) { + write_protoc_insertion_point(w, customize, &format!("enum:{}", enumeration.full_name())); +} + +pub(crate) fn write_protoc_insertion_point_for_enum_value( + w: &mut CodeWriter, + customize: &Customize, + value: &EnumValueDescriptor, +) { + write_protoc_insertion_point(w, customize, &format!("enum_value:{}", value.full_name())); +} + +pub(crate) fn write_protoc_insertion_point_for_oneof( + w: &mut CodeWriter, + customize: &Customize, + oneof: &OneofDescriptor, +) { + write_protoc_insertion_point(w, customize, &format!("oneof:{}", oneof.full_name())); +} + +pub(crate) fn write_protoc_insertion_point_for_oneof_field( + w: &mut CodeWriter, + customize: &Customize, + field: &FieldDescriptor, +) { + write_protoc_insertion_point(w, customize, &format!("oneof_field:{}", field.full_name())); +} diff --git a/src/gen/rust/component.rs b/src/gen/rust/component.rs new file mode 100644 index 0000000..8bf9c85 --- /dev/null +++ b/src/gen/rust/component.rs @@ -0,0 +1,34 @@ +use std::fmt; +use std::fmt::Formatter; + +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::keywords::parse_rust_keyword; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub(crate) enum RustPathComponent { + Ident(RustIdent), + Keyword(&'static str), +} + +impl fmt::Display for RustPathComponent { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + RustPathComponent::Ident(ident) => write!(f, "{}", ident), + RustPathComponent::Keyword(keyword) => write!(f, "{}", keyword), + } + } +} + +impl RustPathComponent { + pub(crate) const SUPER: RustPathComponent = RustPathComponent::Keyword("super"); + + pub(crate) fn parse(s: &str) -> RustPathComponent { + if s.starts_with("r#") { + RustPathComponent::Ident(RustIdent::new(&s[2..])) + } else if let Some(kw) = parse_rust_keyword(s) { + RustPathComponent::Keyword(kw) + } else { + RustPathComponent::Ident(RustIdent::new(s)) + } + } +} diff --git a/src/gen/rust/ident.rs b/src/gen/rust/ident.rs new file mode 100644 index 0000000..df1d1f0 --- /dev/null +++ b/src/gen/rust/ident.rs @@ -0,0 +1,62 @@ +use std::fmt; + +use crate::gen::rust::ident_with_path::RustIdentWithPath; +use crate::gen::rust::keywords::is_rust_keyword; +use crate::gen::rust::rel_path::RustRelativePath; + +/// Valid Rust identifier +#[derive(Eq, PartialEq, Debug, Clone, Hash)] +pub(crate) struct RustIdent(String); + +impl RustIdent { + pub fn new(s: &str) -> RustIdent { + assert!(!s.is_empty()); + assert!(!s.contains("/"), "{}", s); + assert!(!s.contains("."), "{}", s); + assert!(!s.contains(":"), "{}", s); + assert!(!s.contains(" "), "{}", s); + assert!(!s.contains("#"), "{}", s); + RustIdent(s.to_owned()) + } + + pub(crate) fn get(&self) -> &str { + &self.0 + } + + pub fn into_string(self) -> String { + self.0 + } + + pub fn to_path(&self) -> RustIdentWithPath { + RustIdentWithPath::from(&self.0) + } + + pub(crate) fn into_rel_path(self) -> RustRelativePath { + RustRelativePath::from_idents([self]) + } +} + +impl fmt::Display for RustIdent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Rust-protobuf uses `_` suffix to escape identifiers instead of raw identifiers + // because some identifiers cannot be escaped as raw identifiers, + // e.g. `r#self` is not a valid raw identifier. + if is_rust_keyword(&self.0) { + write!(f, "{}_", self.0) + } else { + write!(f, "{}", self.0) + } + } +} + +impl From<&'_ str> for RustIdent { + fn from(s: &str) -> Self { + RustIdent::new(s) + } +} + +impl From<String> for RustIdent { + fn from(s: String) -> Self { + RustIdent::new(&s) + } +} diff --git a/src/gen/rust/ident_with_path.rs b/src/gen/rust/ident_with_path.rs new file mode 100644 index 0000000..bb121a7 --- /dev/null +++ b/src/gen/rust/ident_with_path.rs @@ -0,0 +1,45 @@ +use std::fmt; + +use crate::gen::rust::component::RustPathComponent; +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::path::RustPath; + +#[derive(Eq, PartialEq, Debug, Clone)] +pub(crate) struct RustIdentWithPath { + pub path: RustPath, + pub ident: RustIdent, +} + +impl RustIdentWithPath { + pub fn new(s: String) -> RustIdentWithPath { + let mut path = RustPath::from(s); + let ident = match path.path.path.pop() { + None => panic!("empty path"), + Some(RustPathComponent::Ident(ident)) => ident, + Some(RustPathComponent::Keyword(kw)) => { + panic!("last path component is a keyword: {}", kw) + } + }; + RustIdentWithPath { path, ident } + } + + pub fn prepend_ident(&mut self, ident: RustIdent) { + self.path.prepend_ident(ident) + } + + pub fn to_path(&self) -> RustPath { + self.path.clone().append_ident(self.ident.clone()) + } +} + +impl<S: Into<String>> From<S> for RustIdentWithPath { + fn from(s: S) -> Self { + RustIdentWithPath::new(s.into()) + } +} + +impl fmt::Display for RustIdentWithPath { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.to_path(), f) + } +} diff --git a/src/gen/rust/keywords.rs b/src/gen/rust/keywords.rs new file mode 100644 index 0000000..155977d --- /dev/null +++ b/src/gen/rust/keywords.rs @@ -0,0 +1,86 @@ +#[cfg_attr(rustfmt, rustfmt_skip)] +static RUST_KEYWORDS: &'static [&'static str] = &[ + "_", + "as", + "async", + "await", + "break", + "crate", + "dyn", + "else", + "enum", + "extern", + "false", + "fn", + "for", + "if", + "impl", + "in", + "let", + "loop", + "match", + "mod", + "move", + "mut", + "pub", + "ref", + "return", + "static", + "self", + "Self", + "struct", + "super", + "true", + "trait", + "type", + "unsafe", + "use", + "while", + "continue", + "box", + "const", + "where", + "virtual", + "proc", + "alignof", + "become", + "offsetof", + "priv", + "pure", + "sizeof", + "typeof", + "unsized", + "yield", + "do", + "abstract", + "final", + "override", + "macro", +]; + +// https://internals.rust-lang.org/t/raw-identifiers-dont-work-for-all-identifiers/9094/3 +#[cfg_attr(rustfmt, rustfmt_skip)] +static RUST_KEYWORDS_WHICH_CANNOT_BE_RAW: &'static [&'static str] = &[ + "super", + "self", + "Self", + "extern", + "crate", +]; + +pub(crate) fn parse_rust_keyword(word: &str) -> Option<&'static str> { + RUST_KEYWORDS.iter().cloned().find(|&kw| kw == word) +} + +pub(crate) fn is_rust_keyword(ident: &str) -> bool { + parse_rust_keyword(ident).is_some() +} + +#[allow(dead_code)] +pub(crate) fn is_rust_keyword_which_cannot_be_raw(ident: &str) -> bool { + RUST_KEYWORDS_WHICH_CANNOT_BE_RAW + .iter() + .cloned() + .find(|&kw| kw == ident) + .is_some() +} diff --git a/src/gen/rust/mod.rs b/src/gen/rust/mod.rs new file mode 100644 index 0000000..343ba92 --- /dev/null +++ b/src/gen/rust/mod.rs @@ -0,0 +1,8 @@ +pub(crate) mod component; +pub(crate) mod ident; +pub(crate) mod ident_with_path; +pub(crate) mod keywords; +pub(crate) mod path; +pub(crate) mod quote; +pub(crate) mod rel_path; +pub(crate) mod snippets; diff --git a/src/gen/rust/path.rs b/src/gen/rust/path.rs new file mode 100644 index 0000000..84d38a3 --- /dev/null +++ b/src/gen/rust/path.rs @@ -0,0 +1,99 @@ +use std::fmt; + +use crate::gen::rust::component::RustPathComponent; +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::ident_with_path::RustIdentWithPath; +use crate::gen::rust::rel_path::RustRelativePath; + +#[derive(Default, Eq, PartialEq, Debug, Clone)] +pub(crate) struct RustPath { + pub(crate) absolute: bool, + pub(crate) path: RustRelativePath, +} + +impl RustPath { + pub fn super_path() -> RustPath { + RustPath::from("super") + } + + pub fn is_absolute(&self) -> bool { + self.absolute + } + + pub fn with_ident(self, ident: RustIdent) -> RustIdentWithPath { + RustIdentWithPath { path: self, ident } + } + + pub fn first(&self) -> Option<RustPathComponent> { + assert!(!self.absolute); + self.path.first() + } + + pub fn remove_first(&mut self) -> Option<RustPathComponent> { + assert!(!self.absolute); + self.path.remove_first() + } + + pub fn prepend_ident(&mut self, ident: RustIdent) { + assert!(!self.absolute); + self.path.prepend_ident(ident); + } + + pub fn append(self, path: RustPath) -> RustPath { + if path.absolute { + path + } else { + RustPath { + absolute: self.absolute, + path: self.path.append(path.path), + } + } + } + + pub(crate) fn append_component(mut self, component: RustPathComponent) -> RustPath { + self.path.path.push(component); + self + } + + pub fn append_ident(self, ident: RustIdent) -> RustPath { + self.append_component(RustPathComponent::Ident(ident)) + } + + pub fn append_with_ident(self, path: RustIdentWithPath) -> RustIdentWithPath { + self.append(path.path).with_ident(path.ident) + } + + pub fn into_relative_or_panic(self) -> RustRelativePath { + assert!(!self.absolute); + self.path + } +} + +impl From<&'_ str> for RustPath { + fn from(s: &str) -> Self { + let (s, absolute) = if s.starts_with("::") { + (&s[2..], true) + } else { + (s, false) + }; + RustPath { + absolute, + path: RustRelativePath::from(s), + } + } +} + +impl From<String> for RustPath { + fn from(s: String) -> Self { + RustPath::from(&s[..]) + } +} + +impl fmt::Display for RustPath { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.absolute { + write!(f, "::")?; + } + write!(f, "{}", self.path) + } +} diff --git a/src/gen/rust/quote.rs b/src/gen/rust/quote.rs new file mode 100644 index 0000000..f01b4ee --- /dev/null +++ b/src/gen/rust/quote.rs @@ -0,0 +1,58 @@ +fn hex_digit(value: u32) -> char { + if value < 10 { + (b'0' + value as u8) as char + } else if value < 0x10 { + (b'a' + value as u8 - 10) as char + } else { + unreachable!() + } +} + +pub fn quote_escape_str(s: &str) -> String { + let mut buf = String::new(); + buf.push('"'); + buf.extend(s.chars().flat_map(|c| c.escape_default())); + buf.push('"'); + buf +} + +pub fn quote_escape_bytes(bytes: &[u8]) -> String { + let mut buf = String::new(); + buf.push('b'); + buf.push('"'); + for &b in bytes { + match b { + b'\n' => buf.push_str(r"\n"), + b'\r' => buf.push_str(r"\r"), + b'\t' => buf.push_str(r"\t"), + b'"' => buf.push_str("\\\""), + b'\\' => buf.push_str(r"\\"), + b'\x20'..=b'\x7e' => buf.push(b as char), + _ => { + buf.push_str(r"\x"); + buf.push(hex_digit((b as u32) >> 4)); + buf.push(hex_digit((b as u32) & 0x0f)); + } + } + } + buf.push('"'); + buf +} + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn test_quote_escape_bytes() { + assert_eq!("b\"\"", quote_escape_bytes(b"")); + assert_eq!("b\"xyZW\"", quote_escape_bytes(b"xyZW")); + assert_eq!("b\"aa\\\"bb\"", quote_escape_bytes(b"aa\"bb")); + assert_eq!("b\"aa\\r\\n\\tbb\"", quote_escape_bytes(b"aa\r\n\tbb")); + assert_eq!( + "b\"\\x00\\x01\\x12\\xfe\\xff\"", + quote_escape_bytes(b"\x00\x01\x12\xfe\xff") + ); + } +} diff --git a/src/gen/rust/rel_path.rs b/src/gen/rust/rel_path.rs new file mode 100644 index 0000000..6188ce6 --- /dev/null +++ b/src/gen/rust/rel_path.rs @@ -0,0 +1,97 @@ +use std::fmt; +use std::iter; + +use crate::gen::rust::component::RustPathComponent; +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::path::RustPath; + +#[derive(Default, Eq, PartialEq, Debug, Clone)] +pub(crate) struct RustRelativePath { + pub(crate) path: Vec<RustPathComponent>, +} + +impl RustRelativePath { + pub fn into_path(self) -> RustPath { + RustPath { + absolute: false, + path: self, + } + } + + pub fn _empty() -> RustRelativePath { + RustRelativePath { path: Vec::new() } + } + + pub fn from_components<I: IntoIterator<Item = RustPathComponent>>(i: I) -> RustRelativePath { + RustRelativePath { + path: i.into_iter().collect(), + } + } + + pub fn from_idents<I: IntoIterator<Item = RustIdent>>(i: I) -> RustRelativePath { + Self::from_components(i.into_iter().map(RustPathComponent::Ident)) + } + + pub fn is_empty(&self) -> bool { + self.path.is_empty() + } + + pub fn first(&self) -> Option<RustPathComponent> { + self.path.iter().cloned().next() + } + + pub fn remove_first(&mut self) -> Option<RustPathComponent> { + if self.path.is_empty() { + None + } else { + Some(self.path.remove(0)) + } + } + + pub fn prepend_ident(&mut self, ident: RustIdent) { + self.path.insert(0, RustPathComponent::Ident(ident)); + } + + pub fn append(mut self, path: RustRelativePath) -> RustRelativePath { + for c in path.path { + self.path.push(c); + } + self + } + + pub fn push_ident(&mut self, ident: RustIdent) { + self.path.push(RustPathComponent::Ident(ident)); + } + + pub fn append_ident(mut self, ident: RustIdent) -> RustRelativePath { + self.push_ident(ident); + self + } + + pub fn to_reverse(&self) -> RustRelativePath { + RustRelativePath::from_components( + iter::repeat(RustPathComponent::SUPER).take(self.path.len()), + ) + } +} + +impl fmt::Display for RustRelativePath { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (i, c) in self.path.iter().enumerate() { + if i != 0 { + write!(f, "::")?; + } + write!(f, "{}", c)?; + } + Ok(()) + } +} + +impl From<&'_ str> for RustRelativePath { + fn from(s: &str) -> Self { + assert!(!s.starts_with("::"), "path is absolute: {:?}", s); + RustRelativePath { + path: s.split("::").map(RustPathComponent::parse).collect(), + } + } +} diff --git a/src/gen/rust/snippets.rs b/src/gen/rust/snippets.rs new file mode 100644 index 0000000..c10c4a9 --- /dev/null +++ b/src/gen/rust/snippets.rs @@ -0,0 +1,10 @@ +pub(crate) const EXPR_NONE: &str = "::std::option::Option::None"; +pub(crate) const EXPR_VEC_NEW: &str = "::std::vec::Vec::new()"; + +fn expr_vec_with_capacity(capacity: &str) -> String { + format!("::std::vec::Vec::with_capacity({})", capacity) +} + +pub(crate) fn expr_vec_with_capacity_const(capacity: usize) -> String { + expr_vec_with_capacity(&capacity.to_string()) +} diff --git a/src/gen/rust_types_values.rs b/src/gen/rust_types_values.rs new file mode 100644 index 0000000..cd67f2b --- /dev/null +++ b/src/gen/rust_types_values.rs @@ -0,0 +1,617 @@ +use std::cmp; + +use once_cell::sync::Lazy; +use protobuf::descriptor::*; +use protobuf::reflect::FileDescriptor; +use protobuf_parse::ProtobufAbsPath; +use regex::Regex; + +use crate::customize::Customize; +use crate::gen::field::type_ext::TypeExt; +use crate::gen::file_and_mod::FileAndMod; +use crate::gen::inside::protobuf_crate_path; +use crate::gen::message::RustTypeMessage; +use crate::gen::paths::proto_path_to_rust_mod; +use crate::gen::rust::component::RustPathComponent; +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::ident_with_path::RustIdentWithPath; +use crate::gen::rust::path::RustPath; +use crate::gen::rust::rel_path::RustRelativePath; +use crate::gen::rust::snippets::EXPR_NONE; +use crate::gen::rust::snippets::EXPR_VEC_NEW; +use crate::gen::scope::RootScope; +use crate::gen::scope::WithScope; +use crate::gen::strx::capitalize; +use crate::gen::well_known_types::is_well_known_type_full; + +// Represent subset of rust types used in generated code +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum RustType { + // integer: signed?, size in bits + Int(bool, u32), + // param is size in bits + Float(u32), + Bool, + Vec(Box<RustType>), + HashMap(Box<RustType>, Box<RustType>), + String, + // [T], not &[T] + Slice(Box<RustType>), + // str, not &str + Str, + Option(Box<RustType>), + MessageField(Box<RustType>), + // Box<T> + Uniq(Box<RustType>), + // &T + Ref(Box<RustType>), + // protobuf message + Message(RustTypeMessage), + // protobuf enum, not any enum + Enum(RustIdentWithPath, RustIdent, i32), + // protobuf enum or unknown + EnumOrUnknown(RustIdentWithPath, RustIdent, i32), + // oneof enum + Oneof(RustIdentWithPath), + // bytes::Bytes + Bytes, + // chars::Chars + Chars, + // group + Group, +} + +impl RustType { + #[inline] + pub(crate) fn to_code(&self, customize: &Customize) -> String { + match *self { + RustType::Int(true, bits) => format!("i{}", bits), + RustType::Int(false, bits) => format!("u{}", bits), + RustType::Float(bits) => format!("f{}", bits), + RustType::Bool => format!("bool"), + RustType::Vec(ref param) => format!("::std::vec::Vec<{}>", param.to_code(customize)), + RustType::HashMap(ref key, ref value) => format!( + "::std::collections::HashMap<{}, {}>", + key.to_code(customize), + value.to_code(customize) + ), + RustType::String => format!("::std::string::String"), + RustType::Slice(ref param) => format!("[{}]", param.to_code(customize)), + RustType::Str => format!("str"), + RustType::Option(ref param) => { + format!("::std::option::Option<{}>", param.to_code(customize)) + } + RustType::MessageField(ref param) => format!( + "{}::MessageField<{}>", + protobuf_crate_path(customize), + param.to_code(customize) + ), + RustType::Uniq(ref param) => format!("::std::boxed::Box<{}>", param.to_code(customize)), + RustType::Ref(ref param) => format!("&{}", param.to_code(customize)), + RustType::Message(ref name) => format!("{}", name), + RustType::Enum(ref name, ..) | RustType::Oneof(ref name) => format!("{}", name), + RustType::EnumOrUnknown(ref name, ..) => format!( + "{}::EnumOrUnknown<{}>", + protobuf_crate_path(customize), + name + ), + RustType::Group => format!("<group>"), + RustType::Bytes => format!("::bytes::Bytes"), + RustType::Chars => format!("{}::Chars", protobuf_crate_path(customize)), + } + } +} + +impl RustType { + pub(crate) fn u8() -> RustType { + RustType::Int(false, 8) + } + + pub(crate) fn i32() -> RustType { + RustType::Int(true, 32) + } + + /// `&str`. + pub(crate) fn amp_str() -> RustType { + RustType::Str.wrap_ref() + } + + /// `&[u8]`. + pub(crate) fn amp_slice_of_u8() -> RustType { + RustType::u8().wrap_slice().wrap_ref() + } + + /// Type is rust primitive? + pub(crate) fn is_primitive(&self) -> bool { + match *self { + RustType::Int(..) | RustType::Float(..) | RustType::Bool => true, + _ => false, + } + } + + pub fn is_u8(&self) -> bool { + match *self { + RustType::Int(false, 8) => true, + _ => false, + } + } + + pub fn is_copy(&self) -> bool { + if self.is_primitive() { + true + } else if let RustType::Enum(..) = *self { + true + } else if let RustType::EnumOrUnknown(..) = *self { + true + } else { + false + } + } + + fn is_str(&self) -> bool { + match *self { + RustType::Str => true, + _ => false, + } + } + + fn is_string(&self) -> bool { + match *self { + RustType::String => true, + _ => false, + } + } + + fn is_slice(&self) -> Option<&RustType> { + match *self { + RustType::Slice(ref v) => Some(&**v), + _ => None, + } + } + + fn is_slice_u8(&self) -> bool { + match self.is_slice() { + Some(t) => t.is_u8(), + None => false, + } + } + + fn is_message(&self) -> bool { + match *self { + RustType::Message(..) => true, + _ => false, + } + } + + fn is_enum(&self) -> bool { + match *self { + RustType::Enum(..) => true, + _ => false, + } + } + + fn is_enum_or_unknown(&self) -> bool { + match *self { + RustType::EnumOrUnknown(..) => true, + _ => false, + } + } + + pub fn is_ref(&self) -> Option<&RustType> { + match *self { + RustType::Ref(ref v) => Some(&**v), + _ => None, + } + } + + pub fn is_box(&self) -> Option<&RustType> { + match *self { + RustType::Uniq(ref v) => Some(&**v), + _ => None, + } + } + + // default value for type + pub fn default_value(&self, customize: &Customize, const_expr: bool) -> String { + match *self { + RustType::Ref(ref t) if t.is_str() => "\"\"".to_string(), + RustType::Ref(ref t) if t.is_slice().is_some() => "&[]".to_string(), + RustType::Int(..) => "0".to_string(), + RustType::Float(..) => "0.".to_string(), + RustType::Bool => "false".to_string(), + RustType::Vec(..) => EXPR_VEC_NEW.to_string(), + RustType::HashMap(..) => "::std::collections::HashMap::new()".to_string(), + RustType::String => "::std::string::String::new()".to_string(), + RustType::Bytes => "::bytes::Bytes::new()".to_string(), + RustType::Chars => format!("{}::Chars::new()", protobuf_crate_path(customize)), + RustType::Option(..) => EXPR_NONE.to_string(), + RustType::MessageField(..) => { + format!("{}::MessageField::none()", protobuf_crate_path(customize)) + } + RustType::Message(ref name) => format!("{}::new()", name), + RustType::Ref(ref m) if m.is_message() => match **m { + RustType::Message(ref name) => name.default_instance(customize), + _ => unreachable!(), + }, + // Note: default value of enum type may not be equal to default value of field + RustType::Enum(ref name, ref default, ..) => format!("{}::{}", name, default), + RustType::EnumOrUnknown(_, _, number) if const_expr => format!( + "{}::EnumOrUnknown::from_i32({})", + protobuf_crate_path(customize), + number, + ), + RustType::EnumOrUnknown(ref name, ref default, ..) if !const_expr => format!( + "{}::EnumOrUnknown::new({}::{})", + protobuf_crate_path(customize), + name, + default + ), + _ => panic!("cannot create default value for: {:?}", self), + } + } + + pub fn default_value_typed(self, customize: &Customize, const_expr: bool) -> RustValueTyped { + RustValueTyped { + value: self.default_value(customize, const_expr), + rust_type: self, + } + } + + /// Emit a code to clear a variable `v` + pub fn clear(&self, v: &str, customize: &Customize) -> String { + match *self { + RustType::Option(..) => format!("{} = {}", v, EXPR_NONE), + RustType::Vec(..) + | RustType::Bytes + | RustType::Chars + | RustType::String + | RustType::MessageField(..) + | RustType::HashMap(..) => format!("{}.clear()", v), + RustType::Bool + | RustType::Float(..) + | RustType::Int(..) + | RustType::Enum(..) + | RustType::EnumOrUnknown(..) => { + format!("{} = {}", v, self.default_value(customize, false)) + } + ref ty => panic!("cannot clear type: {:?}", ty), + } + } + + // expression to convert `v` of type `self` to type `target` + pub fn into_target(&self, target: &RustType, v: &str, customize: &Customize) -> String { + self.try_into_target(target, v, customize) + .expect(&format!("failed to convert {:?} into {:?}", self, target)) + } + + // https://github.com/rust-lang-nursery/rustfmt/issues/3131 + #[cfg_attr(rustfmt, rustfmt_skip)] + fn try_into_target(&self, target: &RustType, v: &str, customize: &Customize) -> Result<String, ()> { + { + if let Some(t1) = self.is_ref().and_then(|t| t.is_box()) { + if let Some(t2) = target.is_ref() { + if t1 == t2 { + return Ok(format!("&**{}", v)); + } + } + } + } + + match (self, target) { + (x, y) if x == y => return Ok(format!("{}", v)), + (&RustType::Ref(ref x), y) if **x == *y => return Ok(format!("*{}", v)), + (x, &RustType::Uniq(ref y)) if *x == **y => { + return Ok(format!("::std::boxed::Box::new({})", v)) + } + (&RustType::Uniq(ref x), y) if **x == *y => return Ok(format!("*{}", v)), + (&RustType::String, &RustType::Ref(ref t)) if **t == RustType::Str => { + return Ok(format!("&{}", v)) + } + (&RustType::Chars, &RustType::Ref(ref t)) if **t == RustType::Str => { + return Ok(format!("&{}", v)) + } + (&RustType::Ref(ref t1), &RustType::Ref(ref t2)) if t1.is_string() && t2.is_str() => { + return Ok(format!("&{}", v)) + } + (&RustType::Ref(ref t1), &RustType::String) + if match **t1 { + RustType::Str => true, + _ => false, + } => return Ok(format!("{}.to_owned()", v)), + (&RustType::Ref(ref t1), &RustType::Chars) + if match **t1 { + RustType::Str => true, + _ => false, + } => { + return Ok(format!("<{}::Chars as ::std::convert::From<_>>::from({}.to_owned())", + protobuf_crate_path(customize), v)) + }, + (&RustType::Ref(ref t1), &RustType::Vec(ref t2)) + if match (&**t1, &**t2) { + (&RustType::Slice(ref x), ref y) => **x == **y, + _ => false, + } => return Ok(format!("{}.to_vec()", v)), + (&RustType::Ref(ref t1), &RustType::Bytes) + if t1.is_slice_u8() => + return Ok(format!("<::bytes::Bytes as ::std::convert::From<_>>::from({}.to_vec())", v)), + (&RustType::Vec(ref x), &RustType::Ref(ref t)) + if match **t { + RustType::Slice(ref y) => x == y, + _ => false, + } => return Ok(format!("&{}", v)), + (&RustType::Bytes, &RustType::Ref(ref t)) + if match **t { + RustType::Slice(ref y) => **y == RustType::u8(), + _ => false, + } => return Ok(format!("&{}", v)), + (&RustType::Ref(ref t1), &RustType::Ref(ref t2)) + if match (&**t1, &**t2) { + (&RustType::Vec(ref x), &RustType::Slice(ref y)) => x == y, + _ => false, + } => return Ok(format!("&{}", v)), + (&RustType::Enum(..), &RustType::Int(true, 32)) => { + return Ok(format!("{}::Enum::value(&{})", protobuf_crate_path(customize), v)) + }, + (&RustType::EnumOrUnknown(..), &RustType::Int(true, 32)) => { + return Ok(format!("{}::EnumOrUnknown::value(&{})", protobuf_crate_path(customize), v)) + }, + (&RustType::Ref(ref t), &RustType::Int(true, 32)) if t.is_enum() => { + return Ok(format!("{}::Enum::value({})", protobuf_crate_path(customize), v)) + } + (&RustType::Ref(ref t), &RustType::Int(true, 32)) if t.is_enum_or_unknown() => { + return Ok(format!("{}::EnumOrUnknown::value({})", protobuf_crate_path(customize), v)) + }, + (&RustType::EnumOrUnknown(ref f, ..), &RustType::Enum(ref t, ..)) if f == t => { + return Ok(format!("{}::EnumOrUnknown::enum_value_or_default(&{})", protobuf_crate_path(customize), v)) + } + (&RustType::Enum(ref f, ..), &RustType::EnumOrUnknown(ref t, ..)) if f == t => { + return Ok(format!("{}::EnumOrUnknown::new({})", protobuf_crate_path(customize), v)) + } + _ => (), + }; + + if let &RustType::Ref(ref s) = self { + if let Ok(conv) = s.try_into_target(target, v, customize) { + return Ok(conv); + } + } + + Err(()) + } + + /// Type to view data of this type + pub fn ref_type(&self) -> RustType { + RustType::Ref(Box::new(match self { + &RustType::String | &RustType::Chars => RustType::Str, + &RustType::Vec(ref p) => RustType::Slice(p.clone()), + &RustType::Bytes => RustType::Slice(Box::new(RustType::u8())), + &RustType::Message(ref p) => RustType::Message(p.clone()), + &RustType::Uniq(ref p) => RustType::Uniq(p.clone()), + x => panic!("no ref type for {:?}", x), + })) + } + + pub(crate) fn wrap_ref(&self) -> RustType { + RustType::Ref(Box::new(self.clone())) + } + + pub(crate) fn wrap_slice(&self) -> RustType { + RustType::Slice(Box::new(self.clone())) + } + + pub fn elem_type(&self) -> RustType { + match self { + &RustType::Option(ref ty) => (**ty).clone(), + &RustType::MessageField(ref ty) => (**ty).clone(), + x => panic!("cannot get elem type of {:?}", x), + } + } + + // type of `v` in `for v in xxx` + pub fn iter_elem_type(&self) -> RustType { + match self { + &RustType::Vec(ref ty) + | &RustType::Option(ref ty) + | &RustType::MessageField(ref ty) => RustType::Ref(ty.clone()), + x => panic!("cannot iterate {:?}", x), + } + } + + pub fn value(self, value: String) -> RustValueTyped { + RustValueTyped { + value: value, + rust_type: self, + } + } +} + +/// Representation of an expression in code generator: text and type +pub(crate) struct RustValueTyped { + pub value: String, + pub rust_type: RustType, +} + +impl RustValueTyped { + pub fn into_type(&self, target: RustType, customize: &Customize) -> RustValueTyped { + let target_value = self.rust_type.into_target(&target, &self.value, customize); + RustValueTyped { + value: target_value, + rust_type: target, + } + } + + pub fn boxed(self, customize: &Customize) -> RustValueTyped { + self.into_type(RustType::Uniq(Box::new(self.rust_type.clone())), customize) + } +} + +fn file_last_component(file: &str) -> &str { + let bs = file.rfind('\\').map(|i| i + 1).unwrap_or(0); + let fs = file.rfind('/').map(|i| i + 1).unwrap_or(0); + &file[cmp::max(fs, bs)..] +} + +#[cfg(test)] +#[test] +fn test_file_last_component() { + assert_eq!("ab.proto", file_last_component("ab.proto")); + assert_eq!("ab.proto", file_last_component("xx/ab.proto")); + assert_eq!("ab.proto", file_last_component("xx\\ab.proto")); + assert_eq!("ab.proto", file_last_component("yy\\xx\\ab.proto")); +} + +fn is_descriptor_proto(file: &FileDescriptor) -> bool { + file.package() == "google.protobuf" && file_last_component(file.name()) == "descriptor.proto" +} + +fn make_path_to_path(source: &RustRelativePath, dest: &RustPath) -> RustPath { + if dest.is_absolute() { + return dest.clone(); + } + + let mut source = source.clone(); + let mut dest = dest.clone(); + while !source.is_empty() && source.first() == dest.first() { + source.remove_first().unwrap(); + dest.remove_first().unwrap(); + } + source.to_reverse().into_path().append(dest) +} + +pub(crate) fn make_path(source: &RustRelativePath, dest: &RustIdentWithPath) -> RustIdentWithPath { + make_path_to_path(source, &dest.path).with_ident(dest.ident.clone()) +} + +pub(crate) fn message_or_enum_to_rust_relative( + message_or_enum: &dyn WithScope, + current: &FileAndMod, +) -> RustIdentWithPath { + let same_file = message_or_enum.file_descriptor().name() == current.file; + if same_file { + // field type is a message or enum declared in the same file + make_path(¤t.relative_mod, &message_or_enum.rust_name_to_file()) + } else if let Some(name) = is_well_known_type_full(&message_or_enum.name_absolute()) { + // Well-known types are included in rust-protobuf library + // https://developers.google.com/protocol-buffers/docs/reference/google.protobuf + let file_descriptor = message_or_enum.file_descriptor(); + static REGEX: Lazy<Regex> = + Lazy::new(|| Regex::new(r"^google/protobuf/([^/]+\.proto)$").unwrap()); + let captures = REGEX + .captures(file_descriptor.name()) + .unwrap_or_else(|| panic!("`{}` does not match the regex", file_descriptor.name())); + let file_name = captures.get(1).unwrap().as_str(); + let mod_name = proto_path_to_rust_mod(file_name); + RustIdentWithPath::from(format!( + "{protobuf_crate}::well_known_types::{mod_name}::{name}", + protobuf_crate = protobuf_crate_path(¤t.customize), + )) + } else if is_descriptor_proto(&message_or_enum.file_descriptor()) { + // Messages defined in descriptor.proto + RustIdentWithPath::from(format!( + "{}::descriptor::{}", + protobuf_crate_path(¤t.customize), + message_or_enum.rust_name_to_file() + )) + } else { + current + .relative_mod + .to_reverse() + .into_path() + .append_component(RustPathComponent::SUPER) + .append_with_ident(message_or_enum.rust_name_with_file()) + } +} + +pub(crate) fn type_name_to_rust_relative( + type_name: &ProtobufAbsPath, + current: &FileAndMod, + root_scope: &RootScope, +) -> RustIdentWithPath { + assert!(!type_name.is_root()); + let message_or_enum = root_scope.find_message_or_enum(type_name); + message_or_enum_to_rust_relative(&message_or_enum, current) +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PrimitiveTypeVariant { + Default, + TokioBytes, +} + +pub enum _TokioBytesType { + Bytes, + Chars, +} + +// ProtobufType trait name +pub(crate) enum ProtobufTypeGen { + Primitive(field_descriptor_proto::Type, PrimitiveTypeVariant), + Message(RustTypeMessage), + EnumOrUnknown(RustIdentWithPath), +} + +impl ProtobufTypeGen { + pub(crate) fn protobuf_value(&self, customize: &Customize) -> String { + match self { + ProtobufTypeGen::Primitive(t, PrimitiveTypeVariant::Default) => { + t.rust_type().to_code(customize) + } + ProtobufTypeGen::Primitive(_, PrimitiveTypeVariant::TokioBytes) => unimplemented!(), + ProtobufTypeGen::Message(m) => m.0.to_string(), + ProtobufTypeGen::EnumOrUnknown(e) => format!( + "{protobuf_crate}::EnumOrUnknown<{e}>", + protobuf_crate = protobuf_crate_path(customize) + ), + } + } + + pub(crate) fn _rust_type(&self, customize: &Customize) -> String { + match self { + &ProtobufTypeGen::Primitive(t, PrimitiveTypeVariant::Default) => format!( + "{}::reflect::types::ProtobufType{}", + protobuf_crate_path(customize), + capitalize(t.protobuf_name()) + ), + &ProtobufTypeGen::Primitive( + field_descriptor_proto::Type::TYPE_BYTES, + PrimitiveTypeVariant::TokioBytes, + ) => format!( + "{}::reflect::types::ProtobufTypeTokioBytes", + protobuf_crate_path(customize) + ), + &ProtobufTypeGen::Primitive( + field_descriptor_proto::Type::TYPE_STRING, + PrimitiveTypeVariant::TokioBytes, + ) => format!( + "{}::reflect::types::ProtobufTypeTokioChars", + protobuf_crate_path(customize) + ), + &ProtobufTypeGen::Primitive(.., PrimitiveTypeVariant::TokioBytes) => unreachable!(), + &ProtobufTypeGen::Message(ref name) => format!( + "{}::reflect::types::ProtobufTypeMessage<{}>", + protobuf_crate_path(customize), + name + ), + &ProtobufTypeGen::EnumOrUnknown(ref name) => format!( + "{}::reflect::types::ProtobufTypeEnumOrUnknown<{}>", + protobuf_crate_path(customize), + name + ), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn into_target_ref_box_to_ref() { + let t1 = RustType::Ref(Box::new(RustType::Uniq(Box::new(RustType::Message( + RustTypeMessage::from("Ab"), + ))))); + let t2 = RustType::Ref(Box::new(RustType::Message(RustTypeMessage::from("Ab")))); + + assert_eq!("&**v", t1.into_target(&t2, "v", &Customize::default())); + } +} diff --git a/src/gen/scope.rs b/src/gen/scope.rs new file mode 100644 index 0000000..f70d233 --- /dev/null +++ b/src/gen/scope.rs @@ -0,0 +1,536 @@ +use std::ops::Deref; + +use protobuf::reflect::EnumDescriptor; +use protobuf::reflect::EnumValueDescriptor; +use protobuf::reflect::FieldDescriptor; +use protobuf::reflect::FileDescriptor; +use protobuf::reflect::MessageDescriptor; +use protobuf::reflect::OneofDescriptor; +use protobuf_parse::ProtobufAbsPath; +use protobuf_parse::ProtobufAbsPathRef; +use protobuf_parse::ProtobufIdentRef; +use protobuf_parse::ProtobufRelPath; +use protobuf_parse::ProtobufRelPathRef; + +use crate::customize::Customize; +use crate::gen::field::rust_field_name_for_protobuf_field_name; +use crate::gen::file_and_mod::FileAndMod; +use crate::gen::map::map_entry; +use crate::gen::message::message_name_to_nested_mod_name; +use crate::gen::paths::proto_path_to_rust_mod; +use crate::gen::rust::ident::RustIdent; +use crate::gen::rust::ident_with_path::RustIdentWithPath; +use crate::gen::rust::rel_path::RustRelativePath; +use crate::gen::strx::capitalize; + +pub(crate) struct RootScope<'a> { + pub file_descriptors: &'a [FileDescriptor], +} + +impl<'a> RootScope<'a> { + fn packages(&'a self) -> Vec<FileScope<'a>> { + self.file_descriptors + .iter() + .map(|fd| FileScope { + file_descriptor: fd, + }) + .collect() + } + + // find enum by fully qualified name + pub fn _find_enum(&'a self, fqn: &ProtobufAbsPath) -> EnumWithScope<'a> { + match self.find_message_or_enum(fqn) { + MessageOrEnumWithScope::Enum(e) => e, + _ => panic!("not an enum: {}", fqn), + } + } + + // find message by fully qualified name + pub fn find_message(&'a self, fqn: &ProtobufAbsPath) -> MessageWithScope<'a> { + match self.find_message_or_enum(fqn) { + MessageOrEnumWithScope::Message(m) => m, + _ => panic!("not a message: {}", fqn), + } + } + + // find message or enum by fully qualified name + pub fn find_message_or_enum(&'a self, fqn: &ProtobufAbsPath) -> MessageOrEnumWithScope<'a> { + assert!(!fqn.is_root()); + self.packages() + .into_iter() + .flat_map(|p| p.find_message_or_enum_abs(fqn)) + .next() + .expect(&format!("enum not found by name: {}", fqn)) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct FileScope<'a> { + pub file_descriptor: &'a FileDescriptor, +} + +impl<'a> Deref for FileScope<'a> { + type Target = FileDescriptor; + + fn deref(&self) -> &Self::Target { + self.file_descriptor + } +} + +impl<'a> FileScope<'a> { + fn package(&self) -> ProtobufAbsPath { + ProtobufAbsPath::package_from_file_descriptor(self.file_descriptor) + } + + pub fn to_scope(&self) -> Scope<'a> { + Scope { + file_scope: self.clone(), + path: Vec::new(), + } + } + + fn find_message_or_enum( + &self, + name: &ProtobufRelPathRef, + ) -> Option<MessageOrEnumWithScope<'a>> { + self.find_messages_and_enums() + .into_iter() + .filter(|e| e.protobuf_name_to_package().as_ref() == name) + .next() + } + + fn find_message_or_enum_abs( + &self, + name: &ProtobufAbsPathRef, + ) -> Option<MessageOrEnumWithScope<'a>> { + let name = name.to_owned(); + match name.remove_prefix(&self.package()) { + Some(rem) => self.find_message_or_enum(&rem), + None => None, + } + } + + // find all enums in given file descriptor + pub fn find_enums(&self) -> Vec<EnumWithScope<'a>> { + let mut r = Vec::new(); + + self.to_scope().walk_scopes(|scope| { + r.extend(scope.enums()); + }); + + r + } + + /// Find all messages in given file descriptor + pub fn find_messages(&self) -> Vec<MessageWithScope<'a>> { + let mut r = Vec::new(); + + self.to_scope().walk_scopes(|scope| { + r.extend(scope.messages()); + }); + + r + } + + /// Find all messages in given file descriptor, except map messages + pub fn find_messages_except_map(&self) -> Vec<MessageWithScope<'a>> { + self.find_messages() + .into_iter() + .filter(|m| !m.is_map()) + .collect() + } + + /// find all messages and enums in given file descriptor + pub fn find_messages_and_enums(&self) -> Vec<MessageOrEnumWithScope<'a>> { + let mut r = Vec::new(); + + self.to_scope().walk_scopes(|scope| { + r.extend(scope.messages_and_enums()); + }); + + r + } +} + +#[derive(Clone, Debug)] +pub(crate) struct Scope<'a> { + pub file_scope: FileScope<'a>, + pub path: Vec<MessageDescriptor>, +} + +impl<'a> Scope<'a> { + pub(crate) fn file_descriptor(&self) -> FileDescriptor { + self.file_scope.file_descriptor.clone() + } + + // get message descriptors in this scope + fn message_descriptors(&self) -> Vec<MessageDescriptor> { + if self.path.is_empty() { + self.file_scope.file_descriptor.messages().collect() + } else { + self.path.last().unwrap().nested_messages().collect() + } + } + + // get enum descriptors in this scope + fn enum_descriptors(&self) -> Vec<EnumDescriptor> { + if self.path.is_empty() { + self.file_scope.file_descriptor.enums().collect() + } else { + self.path.last().unwrap().nested_enums().collect() + } + } + + // get messages with attached scopes in this scope + pub fn messages(&self) -> Vec<MessageWithScope<'a>> { + self.message_descriptors() + .into_iter() + .map(|message| MessageWithScope { + scope: self.clone(), + message, + }) + .collect() + } + + // get enums with attached scopes in this scope + pub fn enums(&self) -> Vec<EnumWithScope<'a>> { + self.enum_descriptors() + .into_iter() + .map(|en| EnumWithScope { + scope: self.clone(), + en, + }) + .collect() + } + + // get messages and enums with attached scopes in this scope + pub fn messages_and_enums(&self) -> Vec<MessageOrEnumWithScope<'a>> { + self.messages() + .into_iter() + .map(|m| MessageOrEnumWithScope::Message(m)) + .chain( + self.enums() + .into_iter() + .map(|m| MessageOrEnumWithScope::Enum(m)), + ) + .collect() + } + + // nested scopes, i. e. scopes of nested messages + fn nested_scopes(&self) -> Vec<Scope<'a>> { + self.message_descriptors() + .into_iter() + .map(|m| { + let mut nested = self.clone(); + nested.path.push(m); + nested + }) + .collect() + } + + fn walk_scopes_impl<F: FnMut(&Scope<'a>)>(&self, callback: &mut F) { + (*callback)(self); + + for nested in self.nested_scopes() { + nested.walk_scopes_impl(callback); + } + } + + // apply callback for this scope and all nested scopes + fn walk_scopes<F>(&self, mut callback: F) + where + F: FnMut(&Scope<'a>), + { + self.walk_scopes_impl(&mut callback); + } + + pub fn rust_path_to_file(&self) -> RustRelativePath { + RustRelativePath::from_idents( + self.path + .iter() + .map(|m| message_name_to_nested_mod_name(m.name())), + ) + } + + pub fn path_str(&self) -> String { + let v: Vec<&str> = self.path.iter().map(|m| m.name()).collect(); + v.join(".") + } + + pub fn prefix(&self) -> String { + let path_str = self.path_str(); + if path_str.is_empty() { + path_str + } else { + format!("{}.", path_str) + } + } + + pub fn protobuf_path_to_file(&self) -> ProtobufRelPath { + ProtobufRelPath::from_components(self.path.iter().map(|m| ProtobufIdentRef::new(m.name()))) + } + + pub fn protobuf_absolute_path(&self) -> ProtobufAbsPath { + let mut r = self.file_scope.package(); + r.push_relative(&self.protobuf_path_to_file()); + r + } + + pub fn file_and_mod(&self, customize: Customize) -> FileAndMod { + FileAndMod { + file: self.file_scope.file_descriptor.proto().name().to_owned(), + relative_mod: self.rust_path_to_file(), + customize, + } + } +} + +pub(crate) trait WithScope<'a> { + fn scope(&self) -> &Scope<'a>; + + fn file_descriptor(&self) -> FileDescriptor { + self.scope().file_descriptor() + } + + // message or enum name + fn name(&self) -> &ProtobufIdentRef; + + fn name_to_package(&self) -> String { + let mut r = self.scope().prefix(); + r.push_str(&self.name()); + r + } + + fn protobuf_name_to_package(&self) -> ProtobufRelPath { + let r = self.scope().protobuf_path_to_file(); + r.append_ident(ProtobufIdentRef::new(self.name())) + } + + /// Return absolute name starting with dot + fn name_absolute(&self) -> ProtobufAbsPath { + let mut path = self.scope().protobuf_absolute_path(); + path.push_simple(self.name()); + path + } + + // rust type name of this descriptor + fn rust_name(&self) -> RustIdent { + let rust_name = capitalize(&self.name()); + RustIdent::new(&rust_name) + } + + fn rust_name_to_file(&self) -> RustIdentWithPath { + self.scope() + .rust_path_to_file() + .into_path() + .with_ident(self.rust_name()) + } + + // fully-qualified name of this type + fn rust_name_with_file(&self) -> RustIdentWithPath { + let mut r = self.rust_name_to_file(); + r.prepend_ident(proto_path_to_rust_mod( + self.scope().file_descriptor().name(), + )); + r + } +} + +#[derive(Clone, Debug)] +pub(crate) struct MessageWithScope<'a> { + pub scope: Scope<'a>, + pub message: MessageDescriptor, +} + +impl<'a> WithScope<'a> for MessageWithScope<'a> { + fn scope(&self) -> &Scope<'a> { + &self.scope + } + + fn name(&self) -> &ProtobufIdentRef { + ProtobufIdentRef::new(self.message.name()) + } +} + +impl<'a> MessageWithScope<'a> { + pub fn into_scope(mut self) -> Scope<'a> { + self.scope.path.push(self.message); + self.scope + } + + pub fn to_scope(&self) -> Scope<'a> { + self.clone().into_scope() + } + + pub fn fields(&self) -> Vec<FieldWithContext<'a>> { + self.message + .fields() + .into_iter() + .map(|field| FieldWithContext { + field, + message: self.clone(), + }) + .collect() + } + + pub fn oneofs(&self) -> Vec<OneofWithContext<'a>> { + self.message + .oneofs() + .into_iter() + .map(|oneof| OneofWithContext { + message: self.clone(), + oneof, + }) + .collect() + } + + pub fn mod_name(&self) -> RustIdent { + message_name_to_nested_mod_name(self.message.name()) + } + + /// This message is a special message which is a map. + pub fn is_map(&self) -> bool { + map_entry(self).is_some() + } +} + +#[derive(Clone, Debug)] +pub(crate) struct EnumWithScope<'a> { + pub scope: Scope<'a>, + pub en: EnumDescriptor, +} + +impl<'a> EnumWithScope<'a> { + pub fn values(&self) -> Vec<EnumValueWithContext<'a>> { + self.en + .values() + .into_iter() + .map(|v| EnumValueWithContext { + en: self.clone(), + proto: v, + }) + .collect() + } + + // find enum value by protobuf name + pub fn value_by_name(&self, name: &str) -> EnumValueWithContext<'a> { + self.values() + .into_iter() + .find(|v| v.proto.proto().name() == name) + .unwrap() + } +} + +#[derive(Clone, Debug)] +pub(crate) struct EnumValueWithContext<'a> { + pub en: EnumWithScope<'a>, + pub proto: EnumValueDescriptor, +} + +impl<'a> EnumValueWithContext<'a> { + pub fn rust_name(&self) -> RustIdent { + // TODO: camel case or something. + RustIdent::new(self.proto.name()) + } +} + +impl<'a> WithScope<'a> for EnumWithScope<'a> { + fn scope(&self) -> &Scope<'a> { + &self.scope + } + + fn name(&self) -> &ProtobufIdentRef { + ProtobufIdentRef::new(self.en.name()) + } +} + +pub(crate) enum MessageOrEnumWithScope<'a> { + Message(MessageWithScope<'a>), + Enum(EnumWithScope<'a>), +} + +impl<'a> WithScope<'a> for MessageOrEnumWithScope<'a> { + fn scope(&self) -> &Scope<'a> { + match self { + MessageOrEnumWithScope::Message(m) => m.scope(), + MessageOrEnumWithScope::Enum(e) => e.scope(), + } + } + + fn name(&self) -> &ProtobufIdentRef { + match self { + MessageOrEnumWithScope::Message(m) => m.name(), + MessageOrEnumWithScope::Enum(e) => e.name(), + } + } +} + +#[derive(Clone)] +pub(crate) struct FieldWithContext<'a> { + pub field: FieldDescriptor, + pub message: MessageWithScope<'a>, +} + +impl<'a> Deref for FieldWithContext<'a> { + type Target = FieldDescriptor; + + fn deref(&self) -> &Self::Target { + &self.field + } +} + +impl<'a> FieldWithContext<'a> { + pub fn is_oneof(&self) -> bool { + self.field.containing_oneof().is_some() + } + + pub fn oneof(&self) -> Option<OneofWithContext<'a>> { + match self.field.containing_oneof() { + Some(oneof) => Some(OneofWithContext { + message: self.message.clone(), + oneof, + }), + None => None, + } + } +} + +#[derive(Clone)] +pub(crate) struct OneofVariantWithContext<'a> { + pub oneof: &'a OneofWithContext<'a>, + pub field: FieldDescriptor, +} + +#[derive(Clone)] +pub(crate) struct OneofWithContext<'a> { + pub oneof: OneofDescriptor, + pub message: MessageWithScope<'a>, +} + +impl<'a> OneofWithContext<'a> { + pub fn field_name(&'a self) -> RustIdent { + return rust_field_name_for_protobuf_field_name(self.oneof.name()); + } + + // rust type name of enum + pub fn rust_name(&self) -> RustIdentWithPath { + let type_name = RustIdent::from(capitalize(self.oneof.name())); + self.message + .to_scope() + .rust_path_to_file() + .into_path() + .with_ident(type_name) + } + + pub fn variants(&'a self) -> Vec<OneofVariantWithContext<'a>> { + self.message + .fields() + .into_iter() + .filter(|f| f.field.containing_oneof().as_ref() == Some(&self.oneof)) + .map(|f| OneofVariantWithContext { + oneof: self, + field: f.field, + }) + .collect() + } +} diff --git a/src/gen/strx.rs b/src/gen/strx.rs new file mode 100644 index 0000000..d1b26fa --- /dev/null +++ b/src/gen/strx.rs @@ -0,0 +1,57 @@ +pub fn remove_to<'s, P>(s: &'s str, pattern: P) -> &'s str +where + P: Fn(char) -> bool, +{ + match s.rfind(pattern) { + Some(pos) => &s[(pos + 1)..], + None => s, + } +} + +pub fn remove_suffix<'s>(s: &'s str, suffix: &str) -> &'s str { + if !s.ends_with(suffix) { + s + } else { + &s[..(s.len() - suffix.len())] + } +} + +pub fn capitalize(s: &str) -> String { + if s.is_empty() { + return String::new(); + } + let mut char_indices = s.char_indices(); + char_indices.next().unwrap(); + match char_indices.next() { + None => s.to_uppercase(), + Some((i, _)) => s[..i].to_uppercase() + &s[i..], + } +} + +#[cfg(test)] +mod test { + + use super::capitalize; + use super::remove_suffix; + use super::remove_to; + + #[test] + fn test_remove_to() { + assert_eq!("aaa", remove_to("aaa", |c| c == '.')); + assert_eq!("bbb", remove_to("aaa.bbb", |c| c == '.')); + assert_eq!("ccc", remove_to("aaa.bbb.ccc", |c| c == '.')); + } + + #[test] + fn test_remove_suffix() { + assert_eq!("bbb", remove_suffix("bbbaaa", "aaa")); + assert_eq!("aaa", remove_suffix("aaa", "bbb")); + } + + #[test] + fn test_capitalize() { + assert_eq!("", capitalize("")); + assert_eq!("F", capitalize("f")); + assert_eq!("Foo", capitalize("foo")); + } +} diff --git a/src/gen/well_known_types.rs b/src/gen/well_known_types.rs new file mode 100644 index 0000000..6b0b389 --- /dev/null +++ b/src/gen/well_known_types.rs @@ -0,0 +1,123 @@ +use protobuf_parse::ProtobufAbsPath; +use protobuf_parse::ProtobufRelPath; +use protobuf_parse::ProtobufRelPathRef; + +use crate::compiler_plugin; +use crate::gen::code_writer::CodeWriter; +use crate::gen::paths::proto_path_to_rust_mod; + +pub(crate) static WELL_KNOWN_TYPES_PROTO_FILE_NAMES: &[&str] = &[ + "any.proto", + "api.proto", + "duration.proto", + "empty.proto", + "field_mask.proto", + "source_context.proto", + "struct.proto", + "timestamp.proto", + "type.proto", + "wrappers.proto", +]; + +pub(crate) static WELL_KNOWN_TYPES_PROTO_FILE_FULL_NAMES: &[&str] = &[ + "google/protobuf/any.proto", + "google/protobuf/api.proto", + "google/protobuf/duration.proto", + "google/protobuf/empty.proto", + "google/protobuf/field_mask.proto", + "google/protobuf/source_context.proto", + "google/protobuf/struct.proto", + "google/protobuf/timestamp.proto", + "google/protobuf/type.proto", + "google/protobuf/wrappers.proto", +]; + +static NAMES: &'static [&'static str] = &[ + "Any", + "Api", + "BoolValue", + "BytesValue", + "DoubleValue", + "Duration", + "Empty", + "Enum", + "EnumValue", + "Field", + "Field.Cardinality", + "Field.Kind", + "FieldMask", + "FloatValue", + "Int32Value", + "Int64Value", + "ListValue", + "Method", + "Mixin", + "NullValue", + "Option", + "SourceContext", + "StringValue", + "Struct", + "Syntax", + "Timestamp", + "Type", + "UInt32Value", + "UInt64Value", + "Value", +]; + +fn is_well_known_type(name: &ProtobufRelPathRef) -> bool { + NAMES.iter().any(|&n| n == format!("{}", name)) +} + +pub(crate) fn is_well_known_type_full(name: &ProtobufAbsPath) -> Option<ProtobufRelPath> { + if let Some(rem) = name.remove_prefix(&ProtobufAbsPath::from(".google.protobuf")) { + if is_well_known_type(rem) { + Some(rem.to_owned()) + } else { + None + } + } else { + None + } +} + +pub(crate) fn gen_well_known_types_mod() -> compiler_plugin::GenResult { + let v = CodeWriter::with_no_error(|w| { + w.comment("This file is generated. Do not edit"); + w.comment("@generated"); + w.mod_doc("Generated code for \"well known types\""); + w.mod_doc(""); + w.mod_doc("[This document](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf) describes these types."); + + w.write_line(""); + w.write_line("#![allow(unused_attributes)]"); + w.write_line("#![cfg_attr(rustfmt, rustfmt::skip)]"); + + w.write_line(""); + for m in WELL_KNOWN_TYPES_PROTO_FILE_NAMES { + w.write_line(&format!("pub mod {};", proto_path_to_rust_mod(m))); + } + }); + + compiler_plugin::GenResult { + name: "well_known_types_mod.rs".to_string(), + content: v.into_bytes(), + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_is_well_known_type_full() { + assert_eq!( + Some(ProtobufRelPath::from("BoolValue")), + is_well_known_type_full(&ProtobufAbsPath::from(".google.protobuf.BoolValue")) + ); + assert_eq!( + None, + is_well_known_type_full(&ProtobufAbsPath::from(".google.protobuf.Fgfg")) + ); + } +} diff --git a/src/gen_and_write.rs b/src/gen_and_write.rs new file mode 100644 index 0000000..95d621e --- /dev/null +++ b/src/gen_and_write.rs @@ -0,0 +1,62 @@ +#![doc(hidden)] + +use std::fs; +use std::io; +use std::path::Path; + +use protobuf::descriptor::FileDescriptorProto; +use protobuf_parse::ProtoPathBuf; + +use crate::customize::CustomizeCallback; +use crate::gen::all::gen_all; +use crate::Customize; + +#[derive(Debug, thiserror::Error)] +enum Error { + #[error("output path `{0}` is not a directory")] + OutputIsNotDirectory(String), + #[error("output path `{0}` does not exist or not accessible")] + OutputDoesNotExistOrNotAccssible(String, #[source] io::Error), + #[error("failed to create file `{0}`: {1}")] + FailedToWriteFile(String, #[source] io::Error), +} + +#[doc(hidden)] +pub fn gen_and_write( + file_descriptors: &[FileDescriptorProto], + parser: &str, + files_to_generate: &[ProtoPathBuf], + out_dir: &Path, + customize: &Customize, + customize_callback: &dyn CustomizeCallback, +) -> anyhow::Result<()> { + match out_dir.metadata() { + Ok(m) => { + if !m.is_dir() { + return Err(Error::OutputIsNotDirectory(out_dir.display().to_string()).into()); + } + } + Err(e) => { + return Err( + Error::OutputDoesNotExistOrNotAccssible(out_dir.display().to_string(), e).into(), + ); + } + } + + let results = gen_all( + file_descriptors, + parser, + files_to_generate, + customize, + customize_callback, + )?; + + for r in &results { + let mut file_path = out_dir.to_owned(); + file_path.push(&r.name); + fs::write(&file_path, r.content.as_slice()) + .map_err(|e| Error::FailedToWriteFile(file_path.display().to_string(), e))?; + } + + Ok(()) +} @@ -1,35 +1,68 @@ -//! # Protobuf code generator -//! -//! This crate contains protobuf code generator implementation -//! and a `protoc-gen-rust` `protoc` plugin. -//! -//! This crate: -//! * provides `protoc-gen-rust` plugin for `protoc` command -//! * implement protobuf codegen -//! -//! This crate is not meant to be used directly, in fact, it does not provide any public API -//! (except for `protoc-gen-rust` binary). -//! -//! Code can be generated with either: -//! * `protoc-gen-rust` plugin for `protoc` or -//! * [`protoc-rust`](https://docs.rs/protoc) crate -//! (code generator which depends on `protoc` binary for parsing of `.proto` files) -//! * [`protobuf-codegen-pure`](https://docs.rs/protobuf-codegen-pure) crate, -//! similar API to `protoc-rust`, but uses pure rust parser of `.proto` files. -//! -//! # `protoc-gen-rust` plugin for `protoc` -//! -//! When non-cargo build system is used, consider using standard protobuf code generation pattern: -//! `protoc` command does all the work of handling paths and parsing `.proto` files. -//! When `protoc` is invoked with `--rust_out=` option, it invokes `protoc-gen-rust` plugin. -//! provided by this crate. +//! # Protobuf code generator for `protobuf` crate +//! +//! This crate is useful mostly from `build.rs` scripts to generate `.rs` files during the build. +//! +//! # How to generate code +//! +//! There are three main ways to generate `.rs` files from `.proto` files: +//! * using `protoc` command line tool and `protoc-gen-rust` plugin +//! * using this crate `Codegen` with pure rust parser +//! * using this crate `Codegen` with `protoc` parser +//! +//! Which one should you use depends on your needs. +//! +//! If you are using non-cargo build system (like Bazel), you might prefer +//! using `protoc-gen-rust` plugin for `protoc`. +//! +//! If you build with `cargo`, you probably want to use `Codegen` from this crate. +//! +//! # Protoc parser vs pure rust parser +//! +//! There are two protobuf parsers which can be plugged into this crate: +//! * `protoc`-based parser (`protoc` is a command like utility from Google protobuf) +//! * pure rust parser (`protobuf-parse` crate) +//! +//! `protoc`-based parser is expected to parse `.proto` files very correctly: +//! all Google's protobuf implementations rely on it. +//! +//! While there are no known bugs in `protobuf-parse`, it is not tested very well. +//! Also `protobuf-parse` does not implement certain rarely used features of `.proto` parser, +//! mostly complex message options specified in `.proto` files. +//! I never saw anyone using them, but you have been warned. +//! +//! Note `protoc` command can be obtained from +//! [`protoc-bin-vendored`](https://docs.rs/protoc-bin-vendored) crate. +//! +//! # Example +//! +//! ```no_run +//! # mod protoc_bin_vendored { +//! # pub fn protoc_bin_path() -> Result<std::path::PathBuf, std::io::Error> { +//! # unimplemented!() +//! # } +//! # } +//! // Use this in build.rs +//! protobuf_codegen::Codegen::new() +//! // Use `protoc` parser, optional. +//! .protoc() +//! // Use `protoc-bin-vendored` bundled protoc command, optional. +//! .protoc_path(&protoc_bin_vendored::protoc_bin_path().unwrap()) +//! // All inputs and imports from the inputs must reside in `includes` directories. +//! .includes(&["src/protos"]) +//! // Inputs must reside in some of include paths. +//! .input("src/protos/apple.proto") +//! .input("src/protos/banana.proto") +//! // Specify output directory relative to Cargo output directory. +//! .cargo_out_dir("protos") +//! .run_from_script(); +//! ``` //! -//! When building with cargo, consider using `protoc-rust` or `protobuf-codegen-pure` crates. +//! ## How to use `protoc-gen-rust` //! -//! ## How to use `protoc-gen-rust` if you have to +//! If you have to. //! //! (Note `protoc` can be invoked programmatically with -//! [protoc crate](https://docs.rs/protoc)) +//! [protoc crate](https://docs.rs/protoc/%3E=3.0.0-alpha)) //! //! 0) Install protobuf for `protoc` binary. //! @@ -46,11 +79,11 @@ //! ``` //! //! Protobuf is needed only for code generation, `rust-protobuf` runtime -//! does not use `protobuf` library. +//! does not use C++ protobuf library. //! //! 1) Install `protoc-gen-rust` program (which is `protoc` plugin) //! -//! It can be installed either from source or with `cargo install protobuf` command. +//! It can be installed either from source or with `cargo install protobuf-codegen` command. //! //! 2) Add `protoc-gen-rust` to $PATH //! @@ -68,320 +101,45 @@ //! //! This will generate .rs files in current directory. //! -//! # Version 2 +//! # Customize generate code //! -//! This is documentation for version 2 of the crate. +//! Sometimes generated code need to be adjusted, e. g. to have custom derives. //! -//! [Version 3 of the crate](https://docs.rs/protobuf-codegen/%3E=3.0.0-alpha) -//! (currently in development) encapsulates both `protoc` and pure codegens in this crate. +//! rust-protobuf provides two options to do that: +//! * generated `.rs` files contain `@@protoc_insertion_point(...)` markers +//! (similar markers inserts Google's protobuf generator for C++ or Java). +//! Simple script `sed` one-liners can be used to replace these markers with custom annotations. +//! * `Codegen::customize_callback` can be used to patch generated code +//! when invoked from `build.rs` script. +//! +//! # Serde +//! +//! rust-protobuf since version 3 no longer directly supports serde. +//! +//! Rust-protobuf 3 fully supports: +//! * runtime reflection +//! * JSON parsing and printing via +//! [`protobuf-json-mapping`](https://docs.rs/protobuf-json-mapping) +//! +//! Which covers the most of serde use cases. +//! +//! If you still need serde, generic customization callback (see above) can be used +//! to insert `#[serde(...)]` annotations. +//! +//! [Example project](https://github.com/stepancheg/rust-protobuf/tree/master/protobuf-examples/customize-serde) +//! in the rust-protobuf repository demonstrates how to do it. #![deny(rustdoc::broken_intra_doc_links)] -#![deny(missing_docs)] - -extern crate protobuf; - -use std::collections::hash_map::HashMap; -use std::fmt::Write as FmtWrite; -use std::fs::File; -use std::io; -use std::io::Write; -use std::path::Path; - -use protobuf::compiler_plugin; -use protobuf::descriptor::*; -use protobuf::Message; +mod codegen; +mod compiler_plugin; mod customize; -mod enums; -mod extensions; -mod field; -mod file; -mod file_and_mod; -mod file_descriptor; -#[doc(hidden)] -pub mod float; -mod inside; -mod message; -mod oneof; -mod protobuf_name; -mod rust_name; -mod rust_types_values; -mod serde; -mod well_known_types; - -pub(crate) mod rust; -pub(crate) mod scope; -pub(crate) mod strx; -pub(crate) mod syntax; +mod gen; +pub mod gen_and_write; +pub mod protoc_gen_rust; -use customize::customize_from_rustproto_for_file; -#[doc(hidden)] +pub use codegen::Codegen; pub use customize::Customize; - -pub mod code_writer; - -use inside::protobuf_crate_path; -#[doc(hidden)] -pub use protobuf_name::ProtobufAbsolutePath; -#[doc(hidden)] -pub use protobuf_name::ProtobufIdent; -#[doc(hidden)] -pub use protobuf_name::ProtobufRelativePath; -use scope::FileScope; -use scope::RootScope; - -use self::code_writer::CodeWriter; -use self::enums::*; -use self::extensions::*; -use self::message::*; -use crate::file::proto_path_to_rust_mod; - -fn escape_byte(s: &mut String, b: u8) { - if b == b'\n' { - write!(s, "\\n").unwrap(); - } else if b == b'\r' { - write!(s, "\\r").unwrap(); - } else if b == b'\t' { - write!(s, "\\t").unwrap(); - } else if b == b'\\' || b == b'"' { - write!(s, "\\{}", b as char).unwrap(); - } else if b == b'\0' { - write!(s, "\\0").unwrap(); - // ASCII printable except space - } else if b > 0x20 && b < 0x7f { - write!(s, "{}", b as char).unwrap(); - } else { - write!(s, "\\x{:02x}", b).unwrap(); - } -} - -fn write_file_descriptor_data( - file: &FileDescriptorProto, - customize: &Customize, - w: &mut CodeWriter, -) { - let fdp_bytes = file.write_to_bytes().unwrap(); - w.write_line("static file_descriptor_proto_data: &'static [u8] = b\"\\"); - w.indented(|w| { - const MAX_LINE_LEN: usize = 72; - - let mut s = String::new(); - for &b in &fdp_bytes { - let prev_len = s.len(); - escape_byte(&mut s, b); - let truncate = s.len() > MAX_LINE_LEN; - if truncate { - s.truncate(prev_len); - } - if truncate || s.len() == MAX_LINE_LEN { - write!(s, "\\").unwrap(); - w.write_line(&s); - s.clear(); - } - if truncate { - escape_byte(&mut s, b); - } - } - if !s.is_empty() { - write!(s, "\\").unwrap(); - w.write_line(&s); - s.clear(); - } - }); - w.write_line("\";"); - w.write_line(""); - w.lazy_static( - "file_descriptor_proto_lazy", - &format!( - "{}::descriptor::FileDescriptorProto", - protobuf_crate_path(customize) - ), - customize, - ); - w.write_line(""); - w.def_fn( - &format!( - "parse_descriptor_proto() -> {}::descriptor::FileDescriptorProto", - protobuf_crate_path(customize) - ), - |w| { - w.write_line(&format!( - "{}::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()", - protobuf_crate_path(customize) - )); - }, - ); - w.write_line(""); - w.pub_fn( - &format!( - "file_descriptor_proto() -> &'static {}::descriptor::FileDescriptorProto", - protobuf_crate_path(customize) - ), - |w| { - w.block("file_descriptor_proto_lazy.get(|| {", "})", |w| { - w.write_line("parse_descriptor_proto()"); - }); - }, - ); -} - -struct GenFileResult { - compiler_plugin_result: compiler_plugin::GenResult, - mod_name: String, -} - -fn gen_file( - file: &FileDescriptorProto, - _files_map: &HashMap<&str, &FileDescriptorProto>, - root_scope: &RootScope, - customize: &Customize, -) -> GenFileResult { - // TODO: use it - let mut customize = customize.clone(); - // options specified in invocation have precedence over options specified in file - customize.update_with(&customize_from_rustproto_for_file(file.get_options())); - - let scope = FileScope { - file_descriptor: file, - } - .to_scope(); - let lite_runtime = customize.lite_runtime.unwrap_or_else(|| { - file.get_options().get_optimize_for() == FileOptions_OptimizeMode::LITE_RUNTIME - }); - - let mut v = Vec::new(); - - { - let mut w = CodeWriter::new(&mut v); - - w.write_generated_by("rust-protobuf", "2.27.1"); - w.write_line(&format!("//! Generated file from `{}`", file.get_name())); - if customize.inside_protobuf != Some(true) { - w.write_line(""); - w.write_line("/// Generated files are compatible only with the same version"); - w.write_line("/// of protobuf runtime."); - w.commented(|w| { - w.write_line(&format!( - "const _PROTOBUF_VERSION_CHECK: () = {}::{};", - protobuf_crate_path(&customize), - protobuf::VERSION_IDENT - )); - }) - } - - for message in &scope.get_messages() { - // ignore map entries, because they are not used in map fields - if message.map_entry().is_none() { - w.write_line(""); - MessageGen::new(message, &root_scope, &customize).write(&mut w); - } - } - for enum_type in &scope.get_enums() { - w.write_line(""); - EnumGen::new(enum_type, file, &customize, root_scope).write(&mut w); - } - - write_extensions(file, &root_scope, &mut w, &customize); - - if !lite_runtime { - w.write_line(""); - write_file_descriptor_data(file, &customize, &mut w); - } - } - - GenFileResult { - compiler_plugin_result: compiler_plugin::GenResult { - name: format!("{}.rs", proto_path_to_rust_mod(file.get_name())), - content: v, - }, - mod_name: proto_path_to_rust_mod(file.get_name()).into_string(), - } -} - -fn gen_mod_rs(mods: &[String]) -> compiler_plugin::GenResult { - let mut v = Vec::new(); - let mut w = CodeWriter::new(&mut v); - w.comment("@generated"); - w.write_line(""); - for m in mods { - w.write_line(&format!("pub mod {};", m)); - } - drop(w); - compiler_plugin::GenResult { - name: "mod.rs".to_owned(), - content: v, - } -} - -// This function is also used externally by cargo plugin -// https://github.com/plietar/rust-protobuf-build -// So be careful changing its signature. -#[doc(hidden)] -pub fn gen( - file_descriptors: &[FileDescriptorProto], - files_to_generate: &[String], - customize: &Customize, -) -> Vec<compiler_plugin::GenResult> { - let root_scope = RootScope { - file_descriptors: file_descriptors, - }; - - let mut results: Vec<compiler_plugin::GenResult> = Vec::new(); - let files_map: HashMap<&str, &FileDescriptorProto> = - file_descriptors.iter().map(|f| (f.get_name(), f)).collect(); - - let all_file_names: Vec<&str> = file_descriptors.iter().map(|f| f.get_name()).collect(); - - let mut mods = Vec::new(); - - for file_name in files_to_generate { - let file = files_map.get(&file_name[..]).expect(&format!( - "file not found in file descriptors: {:?}, files: {:?}", - file_name, all_file_names - )); - - let gen_file_result = gen_file(file, &files_map, &root_scope, customize); - results.push(gen_file_result.compiler_plugin_result); - mods.push(gen_file_result.mod_name); - } - - if customize.gen_mod_rs.unwrap_or(false) { - results.push(gen_mod_rs(&mods)); - } - - results -} - -#[doc(hidden)] -pub fn gen_and_write( - file_descriptors: &[FileDescriptorProto], - files_to_generate: &[String], - out_dir: &Path, - customize: &Customize, -) -> io::Result<()> { - let results = gen(file_descriptors, files_to_generate, customize); - - for r in &results { - let mut file_path = out_dir.to_owned(); - file_path.push(&r.name); - let mut file_writer = File::create(&file_path)?; - file_writer.write_all(&r.content)?; - file_writer.flush()?; - } - - Ok(()) -} - -#[doc(hidden)] -pub fn protoc_gen_rust_main() { - compiler_plugin::plugin_main_2(|r| { - let customize = Customize::parse_from_parameter(r.parameter).expect("parse options"); - gen(r.file_descriptors, r.files_to_generate, &customize) - }); -} - -/// Used in protobuf-codegen-identical-test +pub use customize::CustomizeCallback; #[doc(hidden)] -pub fn proto_name_to_rs(name: &str) -> String { - format!("{}.rs", proto_path_to_rust_mod(name)) -} +pub use gen::paths::proto_name_to_rs; diff --git a/src/protoc_gen_rust.rs b/src/protoc_gen_rust.rs new file mode 100644 index 0000000..014db6a --- /dev/null +++ b/src/protoc_gen_rust.rs @@ -0,0 +1,21 @@ +#![doc(hidden)] + +use crate::compiler_plugin; +use crate::customize::CustomizeCallbackDefault; +use crate::gen::all::gen_all; +use crate::Customize; + +#[doc(hidden)] +pub fn protoc_gen_rust_main() { + compiler_plugin::plugin_main(|r| { + let customize = Customize::parse_from_parameter(r.parameter).expect("parse options"); + gen_all( + r.file_descriptors, + "protoc --rust-out=...", + r.files_to_generate, + &customize, + &CustomizeCallbackDefault, + ) + }) + .expect("plugin failed"); +} |