1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
|
// Protocol Buffers - Google's data interchange format
// Copyright 2025 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
extern crate proc_macro;
use quote::{format_ident, quote, ToTokens};
use syn::{
parse::ParseStream, parse_macro_input, parse_quote, punctuated::Punctuated, Error, Expr,
ExprArray, ExprPath, ExprStruct, ExprTuple, FieldValue, Ident, Member, Path, QSelf, Result,
Stmt, Token, Type, TypePath,
};
/// proto! enables the use of Rust struct initialization syntax to create
/// protobuf messages. The macro does its best to try and detect the
/// initialization of submessages, but it is only able to do so while the
/// submessage is being defined as part of the original struct literal.
/// Introducing an expression using () or {} as the value of a field will
/// require another call to this macro in order to return a submessage
/// literal.
///
/// ```rust,ignore
/// /*
/// Given the following proto definition:
/// message Data {
/// int32 number = 1;
/// string letters = 2;
/// Data nested = 3;
/// }
/// */
/// use protobuf_proc_macro::proto_proc;
/// let message = proto_proc!(Data {
/// number: 42,
/// letters: "Hello World",
/// nested: Data {
/// number: {
/// let x = 100;
/// x + 1
/// }
/// }
/// });
/// ```
#[proc_macro]
pub fn proto_proc(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let result = parse_macro_input!(input with parse_and_expand_top_level_struct);
result.to_token_stream().into()
}
fn parse_and_expand_top_level_struct(input: ParseStream) -> Result<Expr> {
expand_struct(input.parse()?, EnclosingContext::TopLevel)
}
/// The context in which an expression (struct or array literal) is being
/// expanded.
///
/// This is needed because it subtly affects the generated code. For example,
/// when expanding a nested message, the field is mutated in-place, but when
/// expanding a message inside an array, a new message is created.
#[derive(Clone)]
enum EnclosingContext {
/// The current expression is the top-level message, directly inside the
/// `proto!` invocation.
TopLevel,
/// The current expression will be assigned to a field of a parent message.
Struct {
/// The name of the enclosing field.
field: Ident,
},
/// The current expression is an element of a repeated field (i.e. array).
Array {
/// The name of the repeated field.
repeated_field: Ident,
},
/// The current expression is (key, value) tuple literal for a map field.
Map { map_field: Ident },
}
impl EnclosingContext {
pub fn repeated_field(&self) -> Ident {
match self {
EnclosingContext::Array { repeated_field } => repeated_field.clone(),
_ => panic!("Can only get the repeated field of an array context"),
}
}
}
fn expand_struct(
ExprStruct { attrs, qself, path, fields, rest, .. }: ExprStruct,
enclosing_context: EnclosingContext,
) -> Result<Expr> {
if let Some(attr) = attrs.first() {
return Err(Error::new_spanned(attr, "unsupported syntax"));
}
let merge = rest.map(|rest| {
quote! {
::protobuf::MergeFrom::merge_from(&mut this, #rest);
}
});
let fields = expand_struct_fields(fields)?;
let this_type = expand_struct_type(qself.clone(), path.clone(), enclosing_context.clone());
let (head, tail) =
expand_struct_head_tail(qself.clone(), path.clone(), enclosing_context.clone())?;
Ok(parse_quote! {{
let mut this: #this_type = #head;
#merge
#(#fields)*
#tail
}})
}
fn expand_struct_fields(fields: Punctuated<FieldValue, Token![,]>) -> Result<Vec<Stmt>> {
fields
.into_iter()
.map(|field| {
let Member::Named(ident) = field.member else {
return Err(Error::new_spanned(field, "field must be an identifier, not an index"));
};
let set_method = format_ident!("set_{}", ident);
let enclosing_context = EnclosingContext::Struct { field: ident };
let expr = match field.expr {
Expr::Struct(struct_) => {
// In a nested message, the value will be mutated in-place, so there is no need
// to call the top-level setter.
let expanded = expand_struct(struct_, enclosing_context)?;
return Ok(parse_quote!(#expanded));
}
Expr::Array(array) => expand_array(array, enclosing_context)?,
expr => expr,
};
Ok(parse_quote! {
this.#set_method(#expr);
})
})
.collect()
}
fn expand_struct_type(
qself: Option<QSelf>,
path: Path,
enclosing_context: EnclosingContext,
) -> Type {
if should_infer_message_type(&qself, &path) {
return parse_quote!(_);
}
let type_path = TypePath { qself, path };
if let EnclosingContext::Struct { .. } = enclosing_context {
// Nested messages use a mutable view type.
parse_quote!(::protobuf::Mut<'_, #type_path>)
} else {
parse_quote!(#type_path)
}
}
/// Builds two expressions: one that initializes the message, and another that
/// returns it (if any).
///
/// If the enclosing context is another message, the child message is mutated
/// in-place, so the returning expression is `None`.
fn expand_struct_head_tail(
qself: Option<QSelf>,
path: Path,
enclosing_context: EnclosingContext,
) -> Result<(Expr, Option<Expr>)> {
match enclosing_context {
EnclosingContext::TopLevel => {
if should_infer_message_type(&qself, &path) {
Err(Error::new_spanned(path, "message type must be specified explicitly"))
} else {
let path = ExprPath { attrs: Vec::new(), qself, path };
Ok((parse_quote!(#path::new()), Some(parse_quote!(this))))
}
}
EnclosingContext::Struct { field } => {
// In a nested message, mutate the field in-place.
let field_mut = format_ident!("{}_mut", field);
Ok((parse_quote!(this.#field_mut()), None))
}
EnclosingContext::Array { repeated_field } => {
// In a message inside an array, create a new message and return it.
// The type trickery is used to infer the message type from the repeated wrapper.
Ok((
parse_quote!(::protobuf::__internal::get_repeated_default_value(
::protobuf::__internal::Private,
this.#repeated_field()
)),
Some(parse_quote!(this)),
))
}
EnclosingContext::Map { map_field } => {
// In a message inside a key value tuple, create a new message and return it.
// The type trickery is used to infer the message type from the map wrapper.
Ok((
parse_quote!(::protobuf::__internal::get_map_default_value(
::protobuf::__internal::Private,
this.#map_field()
)),
Some(parse_quote!(this)),
))
}
}
}
/// Returns `true` if the given path is `__` (two underscores), which indicates
/// an inferred type.
fn should_infer_message_type(qself: &Option<QSelf>, path: &Path) -> bool {
qself.is_none() && path.get_ident().is_some_and(|ident| *ident == "__")
}
fn expand_array(array: ExprArray, enclosing_context: EnclosingContext) -> Result<Expr> {
if let Some(attr) = array.attrs.first() {
return Err(Error::new_spanned(attr, "unsupported syntax"));
}
let enclosing_context = EnclosingContext::Array {
repeated_field: match enclosing_context {
EnclosingContext::Struct { field } => field,
EnclosingContext::TopLevel
| EnclosingContext::Array { .. }
| EnclosingContext::Map { .. } => {
return Err(Error::new_spanned(array, "arrays must be nested inside a message"))
}
},
};
let array = array
.elems
.into_iter()
.map(|elem| match elem {
Expr::Struct(struct_) => expand_struct(struct_, enclosing_context.clone()),
Expr::Array(array) => expand_array(array, enclosing_context.clone()),
Expr::Tuple(tuple) => expand_tuple(
tuple,
EnclosingContext::Map { map_field: enclosing_context.repeated_field() },
),
expr => Ok(expr),
})
.collect::<Result<Vec<Expr>>>()?;
Ok(parse_quote!([#(#array),*].into_iter()))
}
fn expand_tuple(tuple: ExprTuple, enclosing_context: EnclosingContext) -> Result<Expr> {
if let Some(attr) = tuple.attrs.first() {
return Err(Error::new_spanned(attr, "unsupported syntax"));
}
if tuple.elems.len() != 2 {
return Err(Error::new_spanned(tuple, "Map tuple literals must have exactly two elements"));
}
let key = tuple.elems[0].clone();
let value = match &tuple.elems[1] {
Expr::Struct(struct_) => expand_struct(struct_.clone(), enclosing_context.clone()),
expr => Ok(expr.clone()),
}?;
Ok(parse_quote!((#key, #value)))
}
|