summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNadrieril2019-03-26 23:28:34 +0100
committerNadrieril2019-03-26 23:56:26 +0100
commit173e0eb15b33342ec7c3523be0f913a962e7b85a (patch)
treea54201bd6fa01d045e73954510b0f3f2b060009f
parent23e12ffc4421414abbd089759dab9c50aefeac0c (diff)
Derive DhallType for anonymous structs and enums
-rw-r--r--dhall/tests/dhall_type.rs24
-rw-r--r--dhall_generator/src/dhall_type.rs145
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<T>,
}
assert_eq!(<B<'static, bool>>::dhall_type(), A::dhall_type());
+
+ #[derive(DhallType)]
+ #[allow(dead_code)]
+ struct C<T>(T, Option<String>);
+ assert_eq!(
+ <C<bool>>::dhall_type(),
+ <(bool, Option<String>)>::dhall_type()
+ );
+
+ #[derive(DhallType)]
+ #[allow(dead_code)]
+ struct D();
+ assert_eq!(
+ <C<D>>::dhall_type(),
+ dhall_expr!({ _1: {}, _2: Optional Text })
+ );
+
+ #[derive(DhallType)]
+ #[allow(dead_code)]
+ enum E<T> {
+ A(T),
+ B(String),
+ };
+ assert_eq!(<E<bool>>::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<syn::Type>,
+) -> Result<proc_macro2::TokenStream, Error> {
+ 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<syn::Type>,
+) -> Result<proc_macro2::TokenStream, Error> {
+ 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::<Result<Vec<_>, 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<proc_macro2::TokenStream, Error> {
+ 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)
}