Add support for array default values

This commit is contained in:
Lukas Kalbertodt
2022-10-17 11:28:12 +02:00
parent 0677f751aa
commit f0895a8b2f
6 changed files with 142 additions and 45 deletions

View File

@@ -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<Expr>, 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<Expr>, 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<Expr>, 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<T>` 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),* ]) }
},
}
}

View File

@@ -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<Expr>),
}
impl Input {
@@ -173,17 +173,26 @@ impl Field {
}
}
impl Expr {
fn from_lit(lit: syn::Lit) -> Result<Self, Error> {
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<Self, syn::Error> {
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 = <Punctuated<Expr, Token![,]>>::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<InternalAttr, Token![,]>;
type AttrList = Punctuated<InternalAttr, Token![,]>;
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))
}

View File

@@ -62,3 +62,19 @@ pub fn from_env_with<T>(
}.into()),
}
}
/// `serde` does not implement `IntoDeserializer` for fixed size arrays. This
/// helper type is just used for this purpose.
pub struct ArrayIntoDeserializer<T, const N: usize>(pub [T; N]);
impl<'de, T, E, const N: usize> serde::de::IntoDeserializer<'de, E> for ArrayIntoDeserializer<T, N>
where
T: serde::de::IntoDeserializer<'de, E>,
E: serde::de::Error,
{
type Deserializer = serde::de::value::SeqDeserializer<std::array::IntoIter<T, N>, E>;
fn into_deserializer(self) -> Self::Deserializer {
serde::de::value::SeqDeserializer::new(self.0.into_iter())
}
}

View File

@@ -53,6 +53,7 @@ pub enum Expr {
Float(Float),
Integer(Integer),
Bool(bool),
Array(&'static [Expr]),
}
#[derive(Debug, Clone, Copy, PartialEq)]

View File

@@ -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(())
},
}
}
}

View File

@@ -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(())
}
}
}
}