diff options
Diffstat (limited to 'third_party/rust/prost-derive/src/field/mod.rs')
-rw-r--r-- | third_party/rust/prost-derive/src/field/mod.rs | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/third_party/rust/prost-derive/src/field/mod.rs b/third_party/rust/prost-derive/src/field/mod.rs new file mode 100644 index 0000000000..09fef830ef --- /dev/null +++ b/third_party/rust/prost-derive/src/field/mod.rs @@ -0,0 +1,366 @@ +mod group; +mod map; +mod message; +mod oneof; +mod scalar; + +use std::fmt; +use std::slice; + +use anyhow::{bail, Error}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Attribute, Ident, Lit, LitBool, Meta, MetaList, MetaNameValue, NestedMeta}; + +#[derive(Clone)] +pub enum Field { + /// A scalar field. + Scalar(scalar::Field), + /// A message field. + Message(message::Field), + /// A map field. + Map(map::Field), + /// A oneof field. + Oneof(oneof::Field), + /// A group field. + Group(group::Field), +} + +impl Field { + /// Creates a new `Field` from an iterator of field attributes. + /// + /// If the meta items are invalid, an error will be returned. + /// If the field should be ignored, `None` is returned. + pub fn new(attrs: Vec<Attribute>, inferred_tag: Option<u32>) -> Result<Option<Field>, Error> { + let attrs = prost_attrs(attrs); + + // TODO: check for ignore attribute. + + let field = if let Some(field) = scalar::Field::new(&attrs, inferred_tag)? { + Field::Scalar(field) + } else if let Some(field) = message::Field::new(&attrs, inferred_tag)? { + Field::Message(field) + } else if let Some(field) = map::Field::new(&attrs, inferred_tag)? { + Field::Map(field) + } else if let Some(field) = oneof::Field::new(&attrs)? { + Field::Oneof(field) + } else if let Some(field) = group::Field::new(&attrs, inferred_tag)? { + Field::Group(field) + } else { + bail!("no type attribute"); + }; + + Ok(Some(field)) + } + + /// Creates a new oneof `Field` from an iterator of field attributes. + /// + /// If the meta items are invalid, an error will be returned. + /// If the field should be ignored, `None` is returned. + pub fn new_oneof(attrs: Vec<Attribute>) -> Result<Option<Field>, Error> { + let attrs = prost_attrs(attrs); + + // TODO: check for ignore attribute. + + let field = if let Some(field) = scalar::Field::new_oneof(&attrs)? { + Field::Scalar(field) + } else if let Some(field) = message::Field::new_oneof(&attrs)? { + Field::Message(field) + } else if let Some(field) = map::Field::new_oneof(&attrs)? { + Field::Map(field) + } else if let Some(field) = group::Field::new_oneof(&attrs)? { + Field::Group(field) + } else { + bail!("no type attribute for oneof field"); + }; + + Ok(Some(field)) + } + + pub fn tags(&self) -> Vec<u32> { + match *self { + Field::Scalar(ref scalar) => vec![scalar.tag], + Field::Message(ref message) => vec![message.tag], + Field::Map(ref map) => vec![map.tag], + Field::Oneof(ref oneof) => oneof.tags.clone(), + Field::Group(ref group) => vec![group.tag], + } + } + + /// Returns a statement which encodes the field. + pub fn encode(&self, ident: TokenStream) -> TokenStream { + match *self { + Field::Scalar(ref scalar) => scalar.encode(ident), + Field::Message(ref message) => message.encode(ident), + Field::Map(ref map) => map.encode(ident), + Field::Oneof(ref oneof) => oneof.encode(ident), + Field::Group(ref group) => group.encode(ident), + } + } + + /// Returns an expression which evaluates to the result of merging a decoded + /// value into the field. + pub fn merge(&self, ident: TokenStream) -> TokenStream { + match *self { + Field::Scalar(ref scalar) => scalar.merge(ident), + Field::Message(ref message) => message.merge(ident), + Field::Map(ref map) => map.merge(ident), + Field::Oneof(ref oneof) => oneof.merge(ident), + Field::Group(ref group) => group.merge(ident), + } + } + + /// Returns an expression which evaluates to the encoded length of the field. + pub fn encoded_len(&self, ident: TokenStream) -> TokenStream { + match *self { + Field::Scalar(ref scalar) => scalar.encoded_len(ident), + Field::Map(ref map) => map.encoded_len(ident), + Field::Message(ref msg) => msg.encoded_len(ident), + Field::Oneof(ref oneof) => oneof.encoded_len(ident), + Field::Group(ref group) => group.encoded_len(ident), + } + } + + /// Returns a statement which clears the field. + pub fn clear(&self, ident: TokenStream) -> TokenStream { + match *self { + Field::Scalar(ref scalar) => scalar.clear(ident), + Field::Message(ref message) => message.clear(ident), + Field::Map(ref map) => map.clear(ident), + Field::Oneof(ref oneof) => oneof.clear(ident), + Field::Group(ref group) => group.clear(ident), + } + } + + pub fn default(&self) -> TokenStream { + match *self { + Field::Scalar(ref scalar) => scalar.default(), + _ => quote!(::core::default::Default::default()), + } + } + + /// Produces the fragment implementing debug for the given field. + pub fn debug(&self, ident: TokenStream) -> TokenStream { + match *self { + Field::Scalar(ref scalar) => { + let wrapper = scalar.debug(quote!(ScalarWrapper)); + quote! { + { + #wrapper + ScalarWrapper(&#ident) + } + } + } + Field::Map(ref map) => { + let wrapper = map.debug(quote!(MapWrapper)); + quote! { + { + #wrapper + MapWrapper(&#ident) + } + } + } + _ => quote!(&#ident), + } + } + + pub fn methods(&self, ident: &Ident) -> Option<TokenStream> { + match *self { + Field::Scalar(ref scalar) => scalar.methods(ident), + Field::Map(ref map) => map.methods(ident), + _ => None, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Label { + /// An optional field. + Optional, + /// A required field. + Required, + /// A repeated field. + Repeated, +} + +impl Label { + fn as_str(self) -> &'static str { + match self { + Label::Optional => "optional", + Label::Required => "required", + Label::Repeated => "repeated", + } + } + + fn variants() -> slice::Iter<'static, Label> { + const VARIANTS: &[Label] = &[Label::Optional, Label::Required, Label::Repeated]; + VARIANTS.iter() + } + + /// Parses a string into a field label. + /// If the string doesn't match a field label, `None` is returned. + fn from_attr(attr: &Meta) -> Option<Label> { + if let Meta::Path(ref path) = *attr { + for &label in Label::variants() { + if path.is_ident(label.as_str()) { + return Some(label); + } + } + } + None + } +} + +impl fmt::Debug for Label { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl fmt::Display for Label { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// Get the items belonging to the 'prost' list attribute, e.g. `#[prost(foo, bar="baz")]`. +fn prost_attrs(attrs: Vec<Attribute>) -> Vec<Meta> { + attrs + .iter() + .flat_map(Attribute::parse_meta) + .flat_map(|meta| match meta { + Meta::List(MetaList { path, nested, .. }) => { + if path.is_ident("prost") { + nested.into_iter().collect() + } else { + Vec::new() + } + } + _ => Vec::new(), + }) + .flat_map(|attr| -> Result<_, _> { + match attr { + NestedMeta::Meta(attr) => Ok(attr), + NestedMeta::Lit(lit) => bail!("invalid prost attribute: {:?}", lit), + } + }) + .collect() +} + +pub fn set_option<T>(option: &mut Option<T>, value: T, message: &str) -> Result<(), Error> +where + T: fmt::Debug, +{ + if let Some(ref existing) = *option { + bail!("{}: {:?} and {:?}", message, existing, value); + } + *option = Some(value); + Ok(()) +} + +pub fn set_bool(b: &mut bool, message: &str) -> Result<(), Error> { + if *b { + bail!("{}", message); + } else { + *b = true; + Ok(()) + } +} + +/// Unpacks an attribute into a (key, boolean) pair, returning the boolean value. +/// If the key doesn't match the attribute, `None` is returned. +fn bool_attr(key: &str, attr: &Meta) -> Result<Option<bool>, Error> { + if !attr.path().is_ident(key) { + return Ok(None); + } + match *attr { + Meta::Path(..) => Ok(Some(true)), + Meta::List(ref meta_list) => { + // TODO(rustlang/rust#23121): slice pattern matching would make this much nicer. + if meta_list.nested.len() == 1 { + if let NestedMeta::Lit(Lit::Bool(LitBool { value, .. })) = meta_list.nested[0] { + return Ok(Some(value)); + } + } + bail!("invalid {} attribute", key); + } + Meta::NameValue(MetaNameValue { + lit: Lit::Str(ref lit), + .. + }) => lit + .value() + .parse::<bool>() + .map_err(Error::from) + .map(Option::Some), + Meta::NameValue(MetaNameValue { + lit: Lit::Bool(LitBool { value, .. }), + .. + }) => Ok(Some(value)), + _ => bail!("invalid {} attribute", key), + } +} + +/// Checks if an attribute matches a word. +fn word_attr(key: &str, attr: &Meta) -> bool { + if let Meta::Path(ref path) = *attr { + path.is_ident(key) + } else { + false + } +} + +pub(super) fn tag_attr(attr: &Meta) -> Result<Option<u32>, Error> { + if !attr.path().is_ident("tag") { + return Ok(None); + } + match *attr { + Meta::List(ref meta_list) => { + // TODO(rustlang/rust#23121): slice pattern matching would make this much nicer. + if meta_list.nested.len() == 1 { + if let NestedMeta::Lit(Lit::Int(ref lit)) = meta_list.nested[0] { + return Ok(Some(lit.base10_parse()?)); + } + } + bail!("invalid tag attribute: {:?}", attr); + } + Meta::NameValue(ref meta_name_value) => match meta_name_value.lit { + Lit::Str(ref lit) => lit + .value() + .parse::<u32>() + .map_err(Error::from) + .map(Option::Some), + Lit::Int(ref lit) => Ok(Some(lit.base10_parse()?)), + _ => bail!("invalid tag attribute: {:?}", attr), + }, + _ => bail!("invalid tag attribute: {:?}", attr), + } +} + +fn tags_attr(attr: &Meta) -> Result<Option<Vec<u32>>, Error> { + if !attr.path().is_ident("tags") { + return Ok(None); + } + match *attr { + Meta::List(ref meta_list) => { + let mut tags = Vec::with_capacity(meta_list.nested.len()); + for item in &meta_list.nested { + if let NestedMeta::Lit(Lit::Int(ref lit)) = *item { + tags.push(lit.base10_parse()?); + } else { + bail!("invalid tag attribute: {:?}", attr); + } + } + Ok(Some(tags)) + } + Meta::NameValue(MetaNameValue { + lit: Lit::Str(ref lit), + .. + }) => lit + .value() + .split(',') + .map(|s| s.trim().parse::<u32>().map_err(Error::from)) + .collect::<Result<Vec<u32>, _>>() + .map(Some), + _ => bail!("invalid tag attribute: {:?}", attr), + } +} |