From f0895a8b2ff0e606b0012d76bc7e8d15269d6b7b Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Mon, 17 Oct 2022 11:28:12 +0200 Subject: [PATCH] Add support for array default values --- macro/src/gen.rs | 109 +++++++++++++++++++++++++++++++++-------------- macro/src/ir.rs | 37 ++++++++++------ src/internal.rs | 16 +++++++ src/meta.rs | 1 + src/toml.rs | 12 ++++++ src/yaml.rs | 12 ++++++ 6 files changed, 142 insertions(+), 45 deletions(-) diff --git a/macro/src/gen.rs b/macro/src/gen.rs index 2f000f1..409abec 100644 --- a/macro/src/gen.rs +++ b/macro/src/gen.rs @@ -1,5 +1,5 @@ use proc_macro2::{Span, TokenStream}; -use quote::{ToTokens, format_ident, quote, quote_spanned}; +use quote::{format_ident, quote, quote_spanned}; use syn::{Ident, spanned::Spanned}; use crate::ir::{self, Expr, FieldKind, LeafKind}; @@ -135,13 +135,14 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream { input.name, f.name, ); + let expr = default_deserialize_expr(&default); match deserialize_with { None => quote! { - Some(confique::internal::deserialize_default(#default).expect(#msg)) + Some(confique::internal::deserialize_default(#expr).expect(#msg)) }, Some(p) => quote! { - Some(#p(confique::internal::into_deserializer(#default)).expect(#msg)) + Some(#p(confique::internal::into_deserializer(#expr)).expect(#msg)) }, } } @@ -308,7 +309,13 @@ fn gen_meta(input: &ir::Input) -> TokenStream { } FieldKind::Leaf { env, kind: LeafKind::Required { default, ty, .. }, ..} => { let env = env_tokens(env); - let default_value = gen_meta_default(default, &ty); + let default_value = match default { + Some(default) => { + let meta = gen_meta_default(default, Some(&ty)); + quote! { Some(#meta) } + }, + None => quote! { None }, + }; quote! { confique::meta::FieldKind::Leaf { env: #env, @@ -340,7 +347,7 @@ fn gen_meta(input: &ir::Input) -> TokenStream { /// Generates the meta expression of type `meta::Expr` to be used for the /// `default` field. -fn gen_meta_default(default: &Option, ty: &syn::Type) -> TokenStream { +fn gen_meta_default(default: &Expr, ty: Option<&syn::Type>) -> TokenStream { fn int_type_to_variant(suffix: &str) -> Option<&'static str> { match suffix { "u8" => Some("U8"), @@ -373,13 +380,13 @@ fn gen_meta_default(default: &Option, ty: &syn::Type) -> TokenStream { // that. Otherwise we use a default. fn infer_type( suffix: &str, - field_ty: &syn::Type, + field_ty: Option<&syn::Type>, default: &str, map: fn(&str) -> Option<&'static str>, ) -> Ident { let variant = map(suffix) .or_else(|| { - if let syn::Type::Path(syn::TypePath { qself: None, path }) = field_ty { + if let Some(syn::Type::Path(syn::TypePath { qself: None, path })) = field_ty { path.get_ident().and_then(|i| map(&i.to_string())) } else { None @@ -390,35 +397,75 @@ fn gen_meta_default(default: &Option, ty: &syn::Type) -> TokenStream { Ident::new(variant, Span::call_site()) } + /// Tries to extract the type of the item of a field with an array default value. + fn get_item_ty(ty: &syn::Type) -> Option<&syn::Type> { + match ty { + syn::Type::Slice(slice) => Some(&slice.elem), + syn::Type::Array(array) => Some(&*array.elem), + syn::Type::Path(p) => { + // This is the least clear case. We certainly want to cover + // `Vec` but ideally some more cases. On the other hand, we + // just can't really know, so some incorrect guesses are + // definitely expected here. Most are likely filtered out by + // applying `gen_meta_default` to it, but some will result in a + // wrong default value type. But people can always just add a + // prefix to the literal in those cases. + // + // We simply check if the last element in the path has exactly + // one generic type argument, in which case we use that. + let args = match &p.path.segments.last().expect("empty type path").arguments { + syn::PathArguments::AngleBracketed(args) => &args.args, + _ => return None, + }; - if let Some(default) = default { - let v = match default { - Expr::Bool(v) => quote! { confique::meta::Expr::Bool(#v) }, - Expr::Str(s) => quote! { confique::meta::Expr::Str(#s) }, - Expr::Int(i) => { - let variant = infer_type(i.suffix(), ty, "I32", int_type_to_variant); - quote! { confique::meta::Expr::Integer(confique::meta::Integer::#variant(#i)) } - } - Expr::Float(f) => { - let variant = infer_type(f.suffix(), ty, "F64", float_type_to_variant); - quote! { confique::meta::Expr::Float(confique::meta::Float::#variant(#f)) } - } - }; + if args.len() != 1 { + return None; + } - quote! { Some(#v) } - } else { - quote! { None } + match &args[0] { + syn::GenericArgument::Type(t) => Some(t), + _ => None, + } + }, + syn::Type::Reference(r) => get_item_ty(&r.elem), + syn::Type::Group(g) => get_item_ty(&g.elem), + syn::Type::Paren(p) => get_item_ty(&p.elem), + _ => None, + } } + + + let v = match default { + Expr::Bool(v) => quote! { confique::meta::Expr::Bool(#v) }, + Expr::Str(s) => quote! { confique::meta::Expr::Str(#s) }, + Expr::Int(i) => { + let variant = infer_type(i.suffix(), ty, "I32", int_type_to_variant); + quote! { confique::meta::Expr::Integer(confique::meta::Integer::#variant(#i)) } + } + Expr::Float(f) => { + let variant = infer_type(f.suffix(), ty, "F64", float_type_to_variant); + quote! { confique::meta::Expr::Float(confique::meta::Float::#variant(#f)) } + } + Expr::Array(items) => { + let item_type = ty.and_then(get_item_ty); + let items = items.iter().map(|item| gen_meta_default(item, item_type)); + quote! { confique::meta::Expr::Array(&[#( #items ),*]) } + } + }; + + quote! { #v } } -impl ToTokens for ir::Expr { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::Str(lit) => lit.to_tokens(tokens), - Self::Int(lit) => lit.to_tokens(tokens), - Self::Float(lit) => lit.to_tokens(tokens), - Self::Bool(lit) => lit.to_tokens(tokens), - } +fn default_deserialize_expr(expr: &ir::Expr) -> TokenStream { + match expr { + ir::Expr::Str(lit) => quote! { #lit }, + ir::Expr::Int(lit) => quote! { #lit }, + ir::Expr::Float(lit) => quote! { #lit }, + ir::Expr::Bool(lit) => quote! { #lit }, + ir::Expr::Array(arr) => { + let items = arr.iter().map(default_deserialize_expr); + quote! { confique::internal::ArrayIntoDeserializer([ #(#items),* ]) } + }, } } diff --git a/macro/src/ir.rs b/macro/src/ir.rs index df9043f..bcc5ee2 100644 --- a/macro/src/ir.rs +++ b/macro/src/ir.rs @@ -1,6 +1,6 @@ //! Definition of the intermediate representation. -use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned}; +use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned, punctuated::Punctuated}; use crate::util::{is_option, unwrap_option}; @@ -70,7 +70,7 @@ pub(crate) enum Expr { Int(syn::LitInt), Float(syn::LitFloat), Bool(syn::LitBool), - // TODO: arrays? + Array(Vec), } impl Input { @@ -173,17 +173,26 @@ impl Field { } } -impl Expr { - fn from_lit(lit: syn::Lit) -> Result { - match lit { - syn::Lit::Str(l) => Ok(Self::Str(l)), - syn::Lit::Int(l) => Ok(Self::Int(l)), - syn::Lit::Float(l) => Ok(Self::Float(l)), - syn::Lit::Bool(l) => Ok(Self::Bool(l)), +impl Parse for Expr { + fn parse(input: ParseStream) -> Result { + let msg = "invalid default value. Allowed are only: certain literals \ + (string, integer, float, bool), and arrays"; - _ => { - let msg = "only string, integer, float and bool literals are allowed here"; - Err(Error::new(lit.span(), msg)) + if input.peek(syn::token::Bracket) { + let content; + syn::bracketed!(content in input); + + let items = >::parse_terminated(&content)?; + Ok(Self::Array(items.into_iter().collect())) + } else { + let lit: syn::Lit = input.parse() + .map_err(|_| Error::new(input.span(), msg))?; + match lit { + syn::Lit::Str(l) => Ok(Self::Str(l)), + syn::Lit::Int(l) => Ok(Self::Int(l)), + syn::Lit::Float(l) => Ok(Self::Float(l)), + syn::Lit::Bool(l) => Ok(Self::Bool(l)), + _ => Err(Error::new(lit.span(), msg)), } } } @@ -239,7 +248,7 @@ fn extract_internal_attrs( let mut out = InternalAttrs::default(); for attr in internal_attrs { - type AttrList = syn::punctuated::Punctuated; + type AttrList = Punctuated; let parsed_list = attr.parse_args_with(AttrList::parse_terminated)?; for parsed in parsed_list { @@ -315,7 +324,7 @@ impl Parse for InternalAttr { "default" => { let _: Token![=] = input.parse()?; - let expr = Expr::from_lit(input.parse()?)?; + let expr: Expr = input.parse()?; assert_empty_or_comma(input)?; Ok(Self::Default(expr)) } diff --git a/src/internal.rs b/src/internal.rs index 624e88a..009d28e 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -62,3 +62,19 @@ pub fn from_env_with( }.into()), } } + +/// `serde` does not implement `IntoDeserializer` for fixed size arrays. This +/// helper type is just used for this purpose. +pub struct ArrayIntoDeserializer(pub [T; N]); + +impl<'de, T, E, const N: usize> serde::de::IntoDeserializer<'de, E> for ArrayIntoDeserializer +where + T: serde::de::IntoDeserializer<'de, E>, + E: serde::de::Error, +{ + type Deserializer = serde::de::value::SeqDeserializer, E>; + + fn into_deserializer(self) -> Self::Deserializer { + serde::de::value::SeqDeserializer::new(self.0.into_iter()) + } +} diff --git a/src/meta.rs b/src/meta.rs index 954ad80..d8ee954 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -53,6 +53,7 @@ pub enum Expr { Float(Float), Integer(Integer), Bool(bool), + Array(&'static [Expr]), } #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/src/toml.rs b/src/toml.rs index 034239e..3e4ecae 100644 --- a/src/toml.rs +++ b/src/toml.rs @@ -193,6 +193,18 @@ impl fmt::Display for PrintExpr { Expr::Float(v) => v.fmt(f), Expr::Integer(v) => v.fmt(f), Expr::Bool(v) => v.fmt(f), + Expr::Array(items) => { + // TODO: pretty printing of long arrays onto multiple lines? + f.write_char('[')?; + for (i, item) in items.iter().enumerate() { + if i != 0 { + f.write_str(", ")?; + } + PrintExpr(item).fmt(f)?; + } + f.write_char(']')?; + Ok(()) + }, } } } diff --git a/src/yaml.rs b/src/yaml.rs index fe97749..6add498 100644 --- a/src/yaml.rs +++ b/src/yaml.rs @@ -184,6 +184,18 @@ impl fmt::Display for PrintExpr { Expr::Float(v) => v.fmt(f), Expr::Integer(v) => v.fmt(f), Expr::Bool(v) => v.fmt(f), + Expr::Array(items) => { + // TODO: pretty printing of long arrays onto multiple lines? + f.write_char('[')?; + for (i, item) in items.iter().enumerate() { + if i != 0 { + f.write_str(", ")?; + } + PrintExpr(item).fmt(f)?; + } + f.write_char(']')?; + Ok(()) + } } } }