#![allow(clippy::all)] use crate::{symbol::*, Ctxt}; use quote::ToTokens; use syn::{ self, parse::{self, Parse}, Meta::{List, NameValue, Path}, NestedMeta::{Lit, Meta}, }; use proc_macro2::{Group, Span, TokenStream, TokenTree}; #[allow(dead_code)] pub struct AttrsContainer { name: String, pb_struct_type: Option, pb_enum_type: Option, } impl AttrsContainer { /// Extract out the `#[pb(...)]` attributes from an item. pub fn from_ast(cx: &Ctxt, item: &syn::DeriveInput) -> Self { let mut pb_struct_type = ASTAttr::none(cx, PB_STRUCT); let mut pb_enum_type = ASTAttr::none(cx, PB_ENUM); for meta_item in item.attrs.iter().flat_map(|attr| get_meta_items(cx, attr)).flatten() { match &meta_item { // Parse `#[pb(struct = "Type")] Meta(NameValue(m)) if m.path == PB_STRUCT => { if let Ok(into_ty) = parse_lit_into_ty(cx, PB_STRUCT, &m.lit) { pb_struct_type.set_opt(&m.path, Some(into_ty)); } } // Parse `#[pb(enum = "Type")] Meta(NameValue(m)) if m.path == PB_ENUM => { if let Ok(into_ty) = parse_lit_into_ty(cx, PB_ENUM, &m.lit) { pb_enum_type.set_opt(&m.path, Some(into_ty)); } } Meta(meta_item) => { let path = meta_item.path().into_token_stream().to_string().replace(' ', ""); cx.error_spanned_by(meta_item.path(), format!("unknown pb container attribute `{}`", path)); } Lit(lit) => { cx.error_spanned_by(lit, "unexpected literal in pb container attribute"); } } } match &item.data { syn::Data::Struct(_) => { pb_struct_type.set_if_none(default_pb_type(&cx, &item.ident)); } syn::Data::Enum(_) => { pb_enum_type.set_if_none(default_pb_type(&cx, &item.ident)); } _ => {} } AttrsContainer { name: item.ident.to_string(), pb_struct_type: pb_struct_type.get(), pb_enum_type: pb_enum_type.get(), } } pub fn pb_struct_type(&self) -> Option<&syn::Type> { self.pb_struct_type.as_ref() } pub fn pb_enum_type(&self) -> Option<&syn::Type> { self.pb_enum_type.as_ref() } } struct ASTAttr<'c, T> { cx: &'c Ctxt, name: Symbol, tokens: TokenStream, value: Option, } impl<'c, T> ASTAttr<'c, T> { fn none(cx: &'c Ctxt, name: Symbol) -> Self { ASTAttr { cx, name, tokens: TokenStream::new(), value: None, } } fn set(&mut self, obj: A, value: T) { let tokens = obj.into_token_stream(); if self.value.is_some() { self.cx .error_spanned_by(tokens, format!("duplicate attribute `{}`", self.name)); } else { self.tokens = tokens; self.value = Some(value); } } fn set_opt(&mut self, obj: A, value: Option) { if let Some(value) = value { self.set(obj, value); } } fn set_if_none(&mut self, value: T) { if self.value.is_none() { self.value = Some(value); } } fn get(self) -> Option { self.value } #[allow(dead_code)] fn get_with_tokens(self) -> Option<(TokenStream, T)> { match self.value { Some(v) => Some((self.tokens, v)), None => None, } } } pub struct ASTAttrField { #[allow(dead_code)] name: String, pb_index: Option, pb_one_of: bool, skip_serializing: bool, skip_deserializing: bool, serialize_with: Option, deserialize_with: Option, } impl ASTAttrField { /// Extract out the `#[pb(...)]` attributes from a struct field. pub fn from_ast(cx: &Ctxt, index: usize, field: &syn::Field) -> Self { let mut pb_index = ASTAttr::none(cx, PB_INDEX); let mut pb_one_of = BoolAttr::none(cx, PB_ONE_OF); let mut serialize_with = ASTAttr::none(cx, SERIALIZE_WITH); let mut skip_serializing = BoolAttr::none(cx, SKIP_SERIALIZING); let mut deserialize_with = ASTAttr::none(cx, DESERIALIZE_WITH); let mut skip_deserializing = BoolAttr::none(cx, SKIP_DESERIALIZING); let ident = match &field.ident { Some(ident) => ident.to_string(), None => index.to_string(), }; for meta_item in field.attrs.iter().flat_map(|attr| get_meta_items(cx, attr)).flatten() { match &meta_item { // Parse `#[pb(skip)]` Meta(Path(word)) if word == SKIP => { skip_serializing.set_true(word); skip_deserializing.set_true(word); } // Parse '#[pb(index = x)]' Meta(NameValue(m)) if m.path == PB_INDEX => { if let syn::Lit::Int(lit) = &m.lit { pb_index.set(&m.path, lit.clone()); } } // Parse `#[pb(one_of)]` Meta(Path(path)) if path == PB_ONE_OF => { pb_one_of.set_true(path); } // Parse `#[pb(serialize_with = "...")]` Meta(NameValue(m)) if m.path == SERIALIZE_WITH => { if let Ok(path) = parse_lit_into_expr_path(cx, SERIALIZE_WITH, &m.lit) { serialize_with.set(&m.path, path); } } // Parse `#[pb(deserialize_with = "...")]` Meta(NameValue(m)) if m.path == DESERIALIZE_WITH => { if let Ok(path) = parse_lit_into_expr_path(cx, DESERIALIZE_WITH, &m.lit) { deserialize_with.set(&m.path, path); } } Meta(meta_item) => { let path = meta_item.path().into_token_stream().to_string().replace(' ', ""); cx.error_spanned_by(meta_item.path(), format!("unknown field attribute `{}`", path)); } Lit(lit) => { cx.error_spanned_by(lit, "unexpected literal in pb field attribute"); } } } ASTAttrField { name: ident, pb_index: pb_index.get(), pb_one_of: pb_one_of.get(), skip_serializing: skip_serializing.get(), skip_deserializing: skip_deserializing.get(), serialize_with: serialize_with.get(), deserialize_with: deserialize_with.get(), } } #[allow(dead_code)] pub fn pb_index(&self) -> Option { self.pb_index.as_ref().map(|lit| lit.base10_digits().to_string()) } pub fn is_one_of(&self) -> bool { self.pb_one_of } pub fn serialize_with(&self) -> Option<&syn::ExprPath> { self.serialize_with.as_ref() } pub fn deserialize_with(&self) -> Option<&syn::ExprPath> { self.deserialize_with.as_ref() } pub fn skip_serializing(&self) -> bool { self.skip_serializing } pub fn skip_deserializing(&self) -> bool { self.skip_deserializing } } pub enum Default { /// Field must always be specified because it does not have a default. None, /// The default is given by `std::default::Default::default()`. Default, /// The default is given by this function. Path(syn::ExprPath), } #[derive(Debug, Clone)] pub struct EventAttrs { input: Option, output: Option, error_ty: Option, pub ignore: bool, } #[derive(Debug, Clone)] pub struct ASTEnumAttrVariant { pub enum_name: String, pub enum_item_name: String, pub value: String, pub event_attrs: EventAttrs, } impl ASTEnumAttrVariant { pub fn from_ast(ctxt: &Ctxt, ident: &syn::Ident, variant: &syn::Variant, enum_attrs: &[syn::Attribute]) -> Self { let enum_item_name = variant.ident.to_string(); let enum_name = ident.to_string(); let mut value = String::new(); if variant.discriminant.is_some() { if let syn::Expr::Lit(ref expr_list) = variant.discriminant.as_ref().unwrap().1 { let lit_int = if let syn::Lit::Int(ref int_value) = expr_list.lit { int_value } else { unimplemented!() }; value = lit_int.base10_digits().to_string(); } } let event_attrs = get_event_attrs_from(ctxt, &variant.attrs, enum_attrs); ASTEnumAttrVariant { enum_name, enum_item_name, value, event_attrs, } } pub fn event_input(&self) -> Option { self.event_attrs.input.clone() } pub fn event_output(&self) -> Option { self.event_attrs.output.clone() } pub fn event_error(&self) -> String { self.event_attrs.error_ty.as_ref().unwrap().clone() } } fn get_event_attrs_from(ctxt: &Ctxt, variant_attrs: &[syn::Attribute], enum_attrs: &[syn::Attribute]) -> EventAttrs { let mut event_attrs = EventAttrs { input: None, output: None, error_ty: None, ignore: false, }; enum_attrs .iter() .filter(|attr| attr.path.segments.iter().any(|s| s.ident == EVENT_ERR)) .for_each(|attr| { if let Ok(NameValue(named_value)) = attr.parse_meta() { if let syn::Lit::Str(s) = named_value.lit { event_attrs.error_ty = Some(s.value()); } else { eprintln!("❌ {} should not be empty", EVENT_ERR); } } else { eprintln!("❌ Can not find any {} on attr: {:#?}", EVENT_ERR, attr); } }); let mut extract_event_attr = |attr: &syn::Attribute, meta_item: &syn::NestedMeta| match &meta_item { Meta(NameValue(name_value)) => { if name_value.path == EVENT_INPUT { if let syn::Lit::Str(s) = &name_value.lit { let input_type = parse_lit_str(s) .map_err(|_| { ctxt.error_spanned_by(s, format!("failed to parse request deserializer {:?}", s.value())) }) .unwrap(); event_attrs.input = Some(input_type); } } if name_value.path == EVENT_OUTPUT { if let syn::Lit::Str(s) = &name_value.lit { let output_type = parse_lit_str(s) .map_err(|_| { ctxt.error_spanned_by(s, format!("failed to parse response deserializer {:?}", s.value())) }) .unwrap(); event_attrs.output = Some(output_type); } } } Meta(Path(word)) => { if word == EVENT_IGNORE && attr.path == EVENT { event_attrs.ignore = true; } } Lit(s) => ctxt.error_spanned_by(s, "unexpected attribute"), _ => ctxt.error_spanned_by(meta_item, "unexpected attribute"), }; let attr_meta_items_info = variant_attrs .iter() .flat_map(|attr| match get_meta_items(ctxt, attr) { Ok(items) => Some((attr, items)), Err(_) => None, }) .collect::)>>(); for (attr, nested_metas) in attr_meta_items_info { nested_metas .iter() .for_each(|meta_item| extract_event_attr(attr, meta_item)) } // eprintln!("😁{:#?}", event_attrs); event_attrs } pub fn get_meta_items(cx: &Ctxt, attr: &syn::Attribute) -> Result, ()> { if attr.path != PB_ATTRS && attr.path != EVENT { return Ok(vec![]); } // http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html match attr.parse_meta() { Ok(List(meta)) => Ok(meta.nested.into_iter().collect()), Ok(other) => { cx.error_spanned_by(other, "expected #[pb(...)] or or #[event(...)]"); Err(()) } Err(err) => { cx.error_spanned_by(attr, "attribute must be str, e.g. #[pb(xx = \"xxx\")]"); cx.syn_error(err); Err(()) } } } fn parse_lit_into_expr_path(cx: &Ctxt, attr_name: Symbol, lit: &syn::Lit) -> Result { let string = get_lit_str(cx, attr_name, lit)?; parse_lit_str(string).map_err(|_| cx.error_spanned_by(lit, format!("failed to parse path: {:?}", string.value()))) } fn get_lit_str<'a>(cx: &Ctxt, attr_name: Symbol, lit: &'a syn::Lit) -> Result<&'a syn::LitStr, ()> { if let syn::Lit::Str(lit) = lit { Ok(lit) } else { cx.error_spanned_by( lit, format!( "expected pb {} attribute to be a string: `{} = \"...\"`", attr_name, attr_name ), ); Err(()) } } fn parse_lit_into_ty(cx: &Ctxt, attr_name: Symbol, lit: &syn::Lit) -> Result { let string = get_lit_str(cx, attr_name, lit)?; parse_lit_str(string).map_err(|_| { cx.error_spanned_by( lit, format!("failed to parse type: {} = {:?}", attr_name, string.value()), ) }) } pub fn parse_lit_str(s: &syn::LitStr) -> parse::Result where T: Parse, { let tokens = spanned_tokens(s)?; syn::parse2(tokens) } fn spanned_tokens(s: &syn::LitStr) -> parse::Result { let stream = syn::parse_str(&s.value())?; Ok(respan_token_stream(stream, s.span())) } fn respan_token_stream(stream: TokenStream, span: Span) -> TokenStream { stream.into_iter().map(|token| respan_token_tree(token, span)).collect() } fn respan_token_tree(mut token: TokenTree, span: Span) -> TokenTree { if let TokenTree::Group(g) = &mut token { *g = Group::new(g.delimiter(), respan_token_stream(g.stream(), span)); } token.set_span(span); token } fn default_pb_type(ctxt: &Ctxt, ident: &syn::Ident) -> syn::Type { let take_ident = ident.to_string(); let lit_str = syn::LitStr::new(&take_ident, ident.span()); if let Ok(tokens) = spanned_tokens(&lit_str) { if let Ok(pb_struct_ty) = syn::parse2(tokens) { return pb_struct_ty; } } ctxt.error_spanned_by(ident, format!("❌ Can't find {} protobuf struct", take_ident)); panic!() } #[allow(dead_code)] pub fn is_option(ty: &syn::Type) -> bool { let path = match ungroup(ty) { syn::Type::Path(ty) => &ty.path, _ => { return false; } }; let seg = match path.segments.last() { Some(seg) => seg, None => { return false; } }; let args = match &seg.arguments { syn::PathArguments::AngleBracketed(bracketed) => &bracketed.args, _ => { return false; } }; seg.ident == "Option" && args.len() == 1 } #[allow(dead_code)] pub fn ungroup(mut ty: &syn::Type) -> &syn::Type { while let syn::Type::Group(group) = ty { ty = &group.elem; } ty } struct BoolAttr<'c>(ASTAttr<'c, ()>); impl<'c> BoolAttr<'c> { fn none(cx: &'c Ctxt, name: Symbol) -> Self { BoolAttr(ASTAttr::none(cx, name)) } fn set_true(&mut self, obj: A) { self.0.set(obj, ()); } fn get(&self) -> bool { self.0.value.is_some() } }