Skip to content

Commit 808dfd5

Browse files
committed
prost-build: support boxing fields
This allows the user to request boxing of specific fields. This is useful for enum types that might have variants of very different size.
1 parent f63691b commit 808dfd5

File tree

4 files changed

+125
-6
lines changed

4 files changed

+125
-6
lines changed

prost-build/src/code_generator.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -310,10 +310,15 @@ impl<'a> CodeGenerator<'a> {
310310
let ty = self.resolve_type(&field, fq_message_name);
311311

312312
let boxed = !repeated
313-
&& (type_ == Type::Message || type_ == Type::Group)
314-
&& self
315-
.message_graph
316-
.is_nested(field.type_name(), fq_message_name);
313+
&& ((type_ == Type::Message || type_ == Type::Group)
314+
&& self
315+
.message_graph
316+
.is_nested(field.type_name(), fq_message_name))
317+
|| (self
318+
.config
319+
.boxed
320+
.get_first_field(&fq_message_name, field.name())
321+
.is_some());
317322

318323
debug!(
319324
" field: {:?}, type: {:?}, boxed: {}",
@@ -557,10 +562,15 @@ impl<'a> CodeGenerator<'a> {
557562
self.push_indent();
558563
let ty = self.resolve_type(&field, fq_message_name);
559564

560-
let boxed = (type_ == Type::Message || type_ == Type::Group)
565+
let boxed = ((type_ == Type::Message || type_ == Type::Group)
561566
&& self
562567
.message_graph
563-
.is_nested(field.type_name(), fq_message_name);
568+
.is_nested(field.type_name(), fq_message_name))
569+
|| (self
570+
.config
571+
.boxed
572+
.get_first_field(&oneof_name, field.name())
573+
.is_some());
564574

565575
debug!(
566576
" oneof: {:?}, type: {:?}, boxed: {}",
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#[allow(clippy::derive_partial_eq_without_eq)]
2+
#[derive(Clone, PartialEq, ::prost::Message)]
3+
pub struct Container {
4+
#[prost(oneof = "container::Data", tags = "1, 2")]
5+
pub data: ::core::option::Option<container::Data>,
6+
}
7+
/// Nested message and enum types in `Container`.
8+
pub mod container {
9+
#[allow(clippy::derive_partial_eq_without_eq)]
10+
#[derive(Clone, PartialEq, ::prost::Oneof)]
11+
pub enum Data {
12+
#[prost(message, tag = "1")]
13+
Foo(::prost::alloc::boxed::Box<super::Foo>),
14+
#[prost(message, tag = "2")]
15+
Bar(super::Bar),
16+
}
17+
}
18+
#[allow(clippy::derive_partial_eq_without_eq)]
19+
#[derive(Clone, PartialEq, ::prost::Message)]
20+
pub struct Foo {
21+
#[prost(string, tag = "1")]
22+
pub foo: ::prost::alloc::string::String,
23+
}
24+
#[allow(clippy::derive_partial_eq_without_eq)]
25+
#[derive(Clone, PartialEq, ::prost::Message)]
26+
pub struct Bar {
27+
#[prost(message, optional, boxed, tag = "1")]
28+
pub qux: ::core::option::Option<::prost::alloc::boxed::Box<Qux>>,
29+
}
30+
#[allow(clippy::derive_partial_eq_without_eq)]
31+
#[derive(Clone, PartialEq, ::prost::Message)]
32+
pub struct Qux {}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
syntax = "proto3";
2+
3+
package field_attributes;
4+
5+
message Container {
6+
oneof data {
7+
Foo foo = 1;
8+
Bar bar = 2;
9+
}
10+
}
11+
12+
message Foo {
13+
string foo = 1;
14+
}
15+
16+
message Bar {
17+
Qux qux = 1;
18+
}
19+
20+
message Qux {
21+
}

prost-build/src/lib.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ pub struct Config {
247247
message_attributes: PathMap<String>,
248248
enum_attributes: PathMap<String>,
249249
field_attributes: PathMap<String>,
250+
boxed: PathMap<()>,
250251
prost_types: bool,
251252
strip_enum_prefix: bool,
252253
out_dir: Option<PathBuf>,
@@ -558,6 +559,27 @@ impl Config {
558559
self
559560
}
560561

562+
/// Wrap matched fields in a `Box`.
563+
///
564+
/// # Arguments
565+
///
566+
/// **`path`** - a path matching any number of fields. These fields get the attribute.
567+
/// For details about matching fields see [`btree_map`](#method.btree_map).
568+
///
569+
/// # Examples
570+
///
571+
/// ```rust
572+
/// # let mut config = prost_build::Config::new();
573+
/// config.boxed(".my_messages.MyMessageType.my_field");
574+
/// ```
575+
pub fn boxed<P>(&mut self, path: P) -> &mut Self
576+
where
577+
P: AsRef<str>,
578+
{
579+
self.boxed.insert(path.as_ref().to_string(), ());
580+
self
581+
}
582+
561583
/// Configures the code generator to use the provided service generator.
562584
pub fn service_generator(&mut self, service_generator: Box<dyn ServiceGenerator>) -> &mut Self {
563585
self.service_generator = Some(service_generator);
@@ -1215,6 +1237,7 @@ impl default::Default for Config {
12151237
message_attributes: PathMap::default(),
12161238
enum_attributes: PathMap::default(),
12171239
field_attributes: PathMap::default(),
1240+
boxed: PathMap::default(),
12181241
prost_types: true,
12191242
strip_enum_prefix: true,
12201243
out_dir: None,
@@ -1651,6 +1674,39 @@ mod tests {
16511674
}
16521675
}
16531676

1677+
#[test]
1678+
fn test_generate_field_attributes() {
1679+
let _ = env_logger::try_init();
1680+
1681+
let out_dir = std::env::temp_dir();
1682+
1683+
Config::new()
1684+
.out_dir(out_dir.clone())
1685+
.boxed("Container.data.foo")
1686+
.boxed("Bar.qux")
1687+
.compile_protos(
1688+
&["src/fixtures/field_attributes/field_attributes.proto"],
1689+
&["src/fixtures/field_attributes"],
1690+
)
1691+
.unwrap();
1692+
1693+
let out_file = out_dir
1694+
.join("field_attributes.rs")
1695+
.as_path()
1696+
.display()
1697+
.to_string();
1698+
1699+
let content = read_all_content(&out_file).replace("\r\n", "\n");
1700+
let expected_content =
1701+
read_all_content("src/fixtures/field_attributes/_expected_field_attributes.rs")
1702+
.replace("\r\n", "\n");
1703+
assert_eq!(
1704+
expected_content, content,
1705+
"Unexpected content: \n{}",
1706+
content
1707+
);
1708+
}
1709+
16541710
#[test]
16551711
fn deterministic_include_file() {
16561712
let _ = env_logger::try_init();

0 commit comments

Comments
 (0)