| // 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))) | |
| } |