summaryrefslogtreecommitdiffstats
path: root/third_party/rust/smart-default/src/body_impl.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/smart-default/src/body_impl.rs')
-rwxr-xr-xthird_party/rust/smart-default/src/body_impl.rs130
1 files changed, 130 insertions, 0 deletions
diff --git a/third_party/rust/smart-default/src/body_impl.rs b/third_party/rust/smart-default/src/body_impl.rs
new file mode 100755
index 0000000000..a40b11f562
--- /dev/null
+++ b/third_party/rust/smart-default/src/body_impl.rs
@@ -0,0 +1,130 @@
+use proc_macro2::TokenStream;
+
+use syn::DeriveInput;
+use syn::spanned::Spanned;
+use syn::parse::Error;
+use quote::quote;
+
+use default_attr::{DefaultAttr, ConversionStrategy};
+use util::find_only;
+
+pub fn impl_my_derive(input: &DeriveInput) -> Result<TokenStream, Error> {
+ let name = &input.ident;
+ let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+
+ let (default_expr, doc) = match input.data {
+ syn::Data::Struct(ref body) => {
+ let (body_assignment, doc) = default_body_tt(&body.fields)?;
+ (quote! {
+ #name #body_assignment
+ }, format!("Return `{}{}`", name, doc))
+ }
+ syn::Data::Enum(ref body) => {
+ let default_variant = find_only(body.variants.iter(), |variant| {
+ if let Some(meta) = DefaultAttr::find_in_attributes(&variant.attrs)? {
+ if meta.code.is_none() {
+ Ok(true)
+ } else {
+ Err(Error::new(meta.code.span(), "Attribute #[default] on variants should have no value"))
+ }
+ } else {
+ Ok(false)
+ }
+ })?.ok_or_else(|| Error::new(input.span(), "No default variant"))?;
+ let default_variant_name = &default_variant.ident;
+ let (body_assignment, doc) = default_body_tt(&default_variant.fields)?;
+ (quote! {
+ #name :: #default_variant_name #body_assignment
+ }, format!("Return `{}::{}{}`", name, default_variant_name, doc))
+ }
+ syn::Data::Union(_) => {
+ panic!()
+ }
+ };
+ Ok(quote! {
+ #[automatically_derived]
+ impl #impl_generics Default for #name #ty_generics #where_clause {
+ #[doc = #doc]
+ fn default() -> Self {
+ #default_expr
+ }
+ }
+ })
+}
+
+/// Return a token-tree for the default "body" - the part after the name that contains the values.
+/// That is, the `{ ... }` part for structs, the `(...)` part for tuples, and nothing for units.
+fn default_body_tt(body: &syn::Fields) -> Result<(TokenStream, String), Error> {
+ let mut doc = String::new();
+ use std::fmt::Write;
+ let body_tt = match body {
+ &syn::Fields::Named(ref fields) => {
+ doc.push_str(" {");
+ let result = {
+ let field_assignments = fields.named.iter().map(|field| {
+ let field_name = field.ident.as_ref();
+ let (default_value, default_doc) = field_default_expr_and_doc(field)?;
+ write!(&mut doc, "\n {}: {},", field_name.expect("field value in struct is empty"), default_doc).unwrap();
+ // let default_value = default_value.into_token_stream();
+ Ok(quote! { #field_name : #default_value })
+ }).collect::<Result<Vec<_>, Error>>()?;
+ quote!{
+ {
+ #( #field_assignments ),*
+ }
+ }
+ };
+ if (&mut doc).ends_with(",") {
+ doc.pop();
+ doc.push('\n');
+ };
+ doc.push('}');
+ result
+ }
+ &syn::Fields::Unnamed(ref fields) => {
+ doc.push('(');
+ let result = {
+ let field_assignments = fields.unnamed.iter().map(|field| {
+ let (default_value, default_doc) = field_default_expr_and_doc(field)?;
+ write!(&mut doc, "{}, ", default_doc).unwrap();
+ Ok(default_value)
+ }).collect::<Result<Vec<TokenStream>, Error>>()?;
+ quote! {
+ (
+ #( #field_assignments ),*
+ )
+ }
+ };
+ if (&mut doc).ends_with(", ") {
+ doc.pop();
+ doc.pop();
+ };
+ doc.push(')');
+ result
+ }
+ &syn::Fields::Unit => quote!{},
+ };
+ Ok((body_tt, doc))
+}
+
+/// Return a default expression for a field based on it's `#[default = "..."]` attribute. Panic
+/// if there is more than one, of if there is a `#[default]` attribute without value.
+fn field_default_expr_and_doc(field: &syn::Field) -> Result<(TokenStream, String), Error> {
+ if let Some(default_attr) = DefaultAttr::find_in_attributes(&field.attrs)? {
+ let conversion_strategy = default_attr.conversion_strategy();
+ let field_value = default_attr.code.ok_or_else(|| {
+ Error::new(field.span(), "Expected #[default = ...] or #[default(...)]")})?;
+
+ let field_value = match conversion_strategy {
+ ConversionStrategy::NoConversion => field_value,
+ ConversionStrategy::Into => quote!((#field_value).into()),
+ };
+
+ let field_doc = format!("{}", field_value);
+ Ok((field_value, field_doc))
+ } else {
+ Ok((quote! {
+ Default::default()
+ }, "Default::default()".to_owned()))
+ }
+}