Protocol Buffers - Google's data interchange format (grpc依赖)
https://developers.google.com/protocol-buffers/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
7.4 KiB
219 lines
7.4 KiB
google3::import! { |
|
"//third_party/rust/quote/v1:quote"; |
|
"//third_party/rust/syn/v2:syn"; |
|
} |
|
|
|
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, 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, |
|
}, |
|
} |
|
|
|
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)), |
|
)) |
|
} |
|
} |
|
} |
|
|
|
/// 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 { .. } => { |
|
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 => Ok(expr), |
|
}) |
|
.collect::<Result<Vec<Expr>>>()?; |
|
|
|
Ok(parse_quote!([#(#array),*].into_iter())) |
|
}
|
|
|