From 173e0eb15b33342ec7c3523be0f913a962e7b85a Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 26 Mar 2019 23:28:34 +0100 Subject: Derive DhallType for anonymous structs and enums --- dhall/tests/dhall_type.rs | 24 +++++++ dhall_generator/src/dhall_type.rs | 145 ++++++++++++++++++++++++++++++++------ 2 files changed, 147 insertions(+), 22 deletions(-) diff --git a/dhall/tests/dhall_type.rs b/dhall/tests/dhall_type.rs index 0e27ad0..633d7c6 100644 --- a/dhall/tests/dhall_type.rs +++ b/dhall/tests/dhall_type.rs @@ -29,4 +29,28 @@ fn test_dhall_type() { field2: Option, } assert_eq!(>::dhall_type(), A::dhall_type()); + + #[derive(DhallType)] + #[allow(dead_code)] + struct C(T, Option); + assert_eq!( + >::dhall_type(), + <(bool, Option)>::dhall_type() + ); + + #[derive(DhallType)] + #[allow(dead_code)] + struct D(); + assert_eq!( + >::dhall_type(), + dhall_expr!({ _1: {}, _2: Optional Text }) + ); + + #[derive(DhallType)] + #[allow(dead_code)] + enum E { + A(T), + B(String), + }; + assert_eq!(>::dhall_type(), dhall_expr!(< A: Bool | B: Text >)); } diff --git a/dhall_generator/src/dhall_type.rs b/dhall_generator/src/dhall_type.rs index b305ca0..329cd79 100644 --- a/dhall_generator/src/dhall_type.rs +++ b/dhall_generator/src/dhall_type.rs @@ -3,34 +3,135 @@ extern crate proc_macro; use proc_macro::TokenStream; use quote::{quote, quote_spanned}; use syn::spanned::Spanned; -use syn::{parse_macro_input, parse_quote, DeriveInput}; +use syn::Error; +use syn::{parse_quote, DeriveInput}; pub fn derive_dhall_type(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); + TokenStream::from(match derive_dhall_type_inner(input) { + Ok(tokens) => tokens, + Err(err) => err.to_compile_error(), + }) +} + +pub fn derive_for_struct( + data: &syn::DataStruct, + constraints: &mut Vec, +) -> Result { + let fields = match &data.fields { + syn::Fields::Named(fields) => fields + .named + .iter() + .map(|f| { + let name = f.ident.as_ref().unwrap().to_string(); + let ty = &f.ty; + (name, ty) + }) + .collect(), + syn::Fields::Unnamed(fields) => fields + .unnamed + .iter() + .enumerate() + .map(|(i, f)| { + let name = format!("_{}", i + 1); + let ty = &f.ty; + (name, ty) + }) + .collect(), + syn::Fields::Unit => vec![], + }; + let fields = fields.into_iter().map(|(name, ty)| { + constraints.push(ty.clone()); + quote! { + m.insert( + dhall_core::Label::from(#name), + <#ty as dhall::DhallType>::dhall_type() + ); + } + }); + Ok(quote! { dhall_core::rc(dhall_core::Expr::RecordType({ + use std::collections::BTreeMap; + let mut m = BTreeMap::new(); + #(#fields)* + m + })) }) +} + +pub fn derive_for_enum( + data: &syn::DataEnum, + constraints: &mut Vec, +) -> Result { + let variants = data + .variants + .iter() + .map(|v| { + let name = v.ident.to_string(); + let ty = match &v.fields { + syn::Fields::Unnamed(fields) if fields.unnamed.is_empty() => { + Err(Error::new( + v.span(), + "Nullary variants are not supported", + )) + } + syn::Fields::Unnamed(fields) if fields.unnamed.len() > 1 => { + Err(Error::new( + v.span(), + "Variants with more than one field are not supported", + )) + } + syn::Fields::Unnamed(fields) => { + Ok(&fields.unnamed.iter().next().unwrap().ty) + } + syn::Fields::Named(_) => Err(Error::new( + v.span(), + "Named variants are not supported", + )), + syn::Fields::Unit => Err(Error::new( + v.span(), + "Nullary variants are not supported", + )), + }; + let ty = ty?; + constraints.push(ty.clone()); + Ok(quote! { + m.insert( + dhall_core::Label::from(#name), + <#ty as dhall::DhallType>::dhall_type() + ); + }) + }) + .collect::, Error>>()?; + + Ok(quote! { dhall_core::rc(dhall_core::Expr::UnionType({ + use std::collections::BTreeMap; + let mut m = BTreeMap::new(); + #(#variants)* + m + })) }) +} + +pub fn derive_dhall_type_inner( + input: TokenStream, +) -> Result { + let input: DeriveInput = syn::parse_macro_input::parse(input)?; // List of types that must impl DhallType let mut constraints = vec![]; let dhall_type = match &input.data { - syn::Data::Struct(data) => match &data.fields { - syn::Fields::Named(fields) => { - let fields = fields.named.iter() - .map(|f| { - let name = f.ident.as_ref().unwrap().to_string(); - let ty = &f.ty; - constraints.push(ty.clone()); - quote!( m.insert(dhall_core::Label::from(#name), <#ty as dhall::DhallType>::dhall_type()); ) - }); - quote! { dhall_core::rc(dhall_core::Expr::RecordType({ - use std::collections::BTreeMap; - let mut m = BTreeMap::new(); - #(#fields)* - m - })) } - } - _ => quote!(dhall_generator::dhall_expr!(Bool)), - }, - _ => quote!(dhall_generator::dhall_expr!(Bool)), + syn::Data::Struct(data) => derive_for_struct(data, &mut constraints)?, + syn::Data::Enum(data) if data.variants.is_empty() => { + return Err(Error::new( + input.span(), + "Empty enums are not supported", + )) + } + syn::Data::Enum(data) => derive_for_enum(data, &mut constraints)?, + syn::Data::Union(x) => { + return Err(Error::new( + x.union_token.span(), + "Unions are not supported", + )) + } }; let mut generics = input.generics.clone(); @@ -81,5 +182,5 @@ pub fn derive_dhall_type(input: TokenStream) -> TokenStream { } } }; - TokenStream::from(tokens) + Ok(tokens) } -- cgit v1.2.3