Move code around in macro crate

This commit is contained in:
Lukas Kalbertodt
2022-10-17 11:55:29 +02:00
parent f0895a8b2f
commit 4c40a35959
5 changed files with 515 additions and 489 deletions

193
macro/src/gen/meta.rs Normal file
View File

@@ -0,0 +1,193 @@
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::Ident;
use crate::ir::{self, Expr, FieldKind, LeafKind};
/// Generates the whole `const META: ... = ...;` item.
pub(super) fn gen(input: &ir::Input) -> TokenStream {
fn env_tokens(env: &Option<String>) -> TokenStream {
match env {
Some(key) => quote! { Some(#key) },
None => quote! { None },
}
}
let name_str = input.name.to_string();
let doc = &input.doc;
let meta_fields = input.fields.iter().map(|f| {
let name = f.name.to_string();
let doc = &f.doc;
let kind = match &f.kind {
FieldKind::Nested { ty }=> {
quote! {
confique::meta::FieldKind::Nested { meta: &<#ty as confique::Config>::META }
}
}
FieldKind::Leaf { env, kind: LeafKind::Optional { .. }, ..} => {
let env = env_tokens(env);
quote! {
confique::meta::FieldKind::Leaf {
env: #env,
kind: confique::meta::LeafKind::Optional,
}
}
}
FieldKind::Leaf { env, kind: LeafKind::Required { default, ty, .. }, ..} => {
let env = env_tokens(env);
let default_value = match default {
Some(default) => {
let meta = default_value_to_meta_expr(default, Some(&ty));
quote! { Some(#meta) }
},
None => quote! { None },
};
quote! {
confique::meta::FieldKind::Leaf {
env: #env,
kind: confique::meta::LeafKind::Required {
default: #default_value,
},
}
}
}
};
quote! {
confique::meta::Field {
name: #name,
doc: &[ #(#doc),* ],
kind: #kind,
}
}
});
quote! {
const META: confique::meta::Meta = confique::meta::Meta {
name: #name_str,
doc: &[ #(#doc),* ],
fields: &[ #( #meta_fields ),* ],
};
}
}
/// Generates the meta expression of type `meta::Expr` to be used for the
/// `default` field. `ty` is the type of the field that is used to better infer
/// the exact type of the default value.
fn default_value_to_meta_expr(default: &Expr, ty: Option<&syn::Type>) -> TokenStream {
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| default_value_to_meta_expr(item, item_type));
quote! { confique::meta::Expr::Array(&[#( #items ),*]) }
}
}
}
/// Maps an integer type to the `meta::Expr` variant (e.g. `u32` -> `U32`).
fn int_type_to_variant(suffix: &str) -> Option<&'static str> {
match suffix {
"u8" => Some("U8"),
"u16" => Some("U16"),
"u32" => Some("U32"),
"u64" => Some("U64"),
"u128" => Some("U128"),
"usize" => Some("Usize"),
"i8" => Some("I8"),
"i16" => Some("I16"),
"i32" => Some("I32"),
"i64" => Some("I64"),
"i128" => Some("I128"),
"isize" => Some("Isize"),
_ => None,
}
}
/// Maps a float type to the `meta::Expr` variant (e.g. `f32` -> `F32`).
fn float_type_to_variant(suffix: &str) -> Option<&'static str> {
match suffix {
"f32" => Some("F32"),
"f64" => Some("F64"),
_ => None,
}
}
/// Tries to infer the type of an int or float default value.
///
/// To figure out the type of int or float literals, we first look at the type
/// suffix of the literal. If it is specified, we use that. Otherwise we check
/// if the field type is a known float/integer type. If so, we use that.
/// Otherwise we use a default.
fn infer_type(
suffix: &str,
field_ty: Option<&syn::Type>,
default: &str,
map: fn(&str) -> Option<&'static str>,
) -> Ident {
let variant = map(suffix)
.or_else(|| {
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
}
})
.unwrap_or(default);
Ident::new(variant, Span::call_site())
}
/// Tries to extract the type of the item of a field with an array default
/// value. Examples: `&[u32]` -> `u32`, `Vec<String>` -> `String`.
fn get_item_ty(ty: &syn::Type) -> Option<&syn::Type> {
match ty {
// The easy types.
syn::Type::Slice(slice) => Some(&slice.elem),
syn::Type::Array(array) => Some(&*array.elem),
// 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.
syn::Type::Path(p) => {
let args = match &p.path.segments.last().expect("empty type path").arguments {
syn::PathArguments::AngleBracketed(args) => &args.args,
_ => return None,
};
if args.len() != 1 {
return None;
}
match &args[0] {
syn::GenericArgument::Type(t) => Some(t),
_ => None,
}
},
// Just recurse on inner type.
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,
}
}

View File

@@ -2,9 +2,12 @@ use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, quote_spanned};
use syn::{Ident, spanned::Spanned};
use crate::ir::{self, Expr, FieldKind, LeafKind};
use crate::ir::{self, FieldKind, LeafKind};
mod meta;
/// The main function to generate the output token stream from the parse IR.
pub(crate) fn gen(input: ir::Input) -> TokenStream {
let partial_mod = gen_partial_mod(&input);
let config_impl = gen_config_impl(&input);
@@ -15,6 +18,7 @@ pub(crate) fn gen(input: ir::Input) -> TokenStream {
}
}
/// Generates the `impl Config for ... { ... }`.
fn gen_config_impl(input: &ir::Input) -> TokenStream {
let name = &input.name;
let (partial_mod_name, partial_struct_name) = partial_names(&input.name);
@@ -44,7 +48,7 @@ fn gen_config_impl(input: &ir::Input) -> TokenStream {
});
let meta_item = gen_meta(input);
let meta_item = meta::gen(input);
quote! {
#[automatically_derived]
impl confique::Config for #name {
@@ -61,17 +65,8 @@ fn gen_config_impl(input: &ir::Input) -> TokenStream {
}
}
/// Returns the names of the module and struct for the partial type:
/// `(mod_name, struct_name)`.
fn partial_names(original_name: &Ident) -> (Ident, Ident) {
use heck::SnakeCase;
(
format_ident!("confique_partial_{}", original_name.to_string().to_snake_case()),
format_ident!("Partial{original_name}"),
)
}
/// Generates the whole `mod ... { ... }` that defines the partial type and
/// related items.
fn gen_partial_mod(input: &ir::Input) -> TokenStream {
let (mod_name, struct_name) = partial_names(&input.name);
let visibility = &input.visibility;
@@ -135,7 +130,7 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
input.name,
f.name,
);
let expr = default_deserialize_expr(&default);
let expr = default_value_to_deserializable_expr(&default);
match deserialize_with {
None => quote! {
@@ -277,198 +272,32 @@ fn gen_partial_mod(input: &ir::Input) -> TokenStream {
}
}
/// Generates the whole `const META` item.
fn gen_meta(input: &ir::Input) -> TokenStream {
fn env_tokens(env: &Option<String>) -> TokenStream {
match env {
Some(key) => quote! { Some(#key) },
None => quote! { None },
}
}
let name_str = input.name.to_string();
let doc = &input.doc;
let meta_fields = input.fields.iter().map(|f| {
let name = f.name.to_string();
let doc = &f.doc;
let kind = match &f.kind {
FieldKind::Nested { ty }=> {
quote! {
confique::meta::FieldKind::Nested { meta: &<#ty as confique::Config>::META }
}
}
FieldKind::Leaf { env, kind: LeafKind::Optional { .. }, ..} => {
let env = env_tokens(env);
quote! {
confique::meta::FieldKind::Leaf {
env: #env,
kind: confique::meta::LeafKind::Optional,
}
}
}
FieldKind::Leaf { env, kind: LeafKind::Required { default, ty, .. }, ..} => {
let env = env_tokens(env);
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,
kind: confique::meta::LeafKind::Required {
default: #default_value,
},
}
}
}
};
quote! {
confique::meta::Field {
name: #name,
doc: &[ #(#doc),* ],
kind: #kind,
}
}
});
quote! {
const META: confique::meta::Meta = confique::meta::Meta {
name: #name_str,
doc: &[ #(#doc),* ],
fields: &[ #( #meta_fields ),* ],
};
}
/// Returns the names of the module and struct for the partial type:
/// `(mod_name, struct_name)`.
fn partial_names(original_name: &Ident) -> (Ident, Ident) {
use heck::SnakeCase;
(
format_ident!("confique_partial_{}", original_name.to_string().to_snake_case()),
format_ident!("Partial{original_name}"),
)
}
/// Generates the meta expression of type `meta::Expr` to be used for the
/// `default` field.
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"),
"u16" => Some("U16"),
"u32" => Some("U32"),
"u64" => Some("U64"),
"u128" => Some("U128"),
"usize" => Some("Usize"),
"i8" => Some("I8"),
"i16" => Some("I16"),
"i32" => Some("I32"),
"i64" => Some("I64"),
"i128" => Some("I128"),
"isize" => Some("Isize"),
_ => None,
}
}
fn float_type_to_variant(suffix: &str) -> Option<&'static str> {
match suffix {
"f32" => Some("F32"),
"f64" => Some("F64"),
_ => None,
}
}
// To figure out the type of int or float literals, we first look at the
// type suffix of the literal. If it is specified, we use that. Otherwise
// we check if the field type is a known float/integer type. If so, we use
// that. Otherwise we use a default.
fn infer_type(
suffix: &str,
field_ty: Option<&syn::Type>,
default: &str,
map: fn(&str) -> Option<&'static str>,
) -> Ident {
let variant = map(suffix)
.or_else(|| {
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
}
})
.unwrap_or(default);
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 args.len() != 1 {
return 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 }
}
fn default_deserialize_expr(expr: &ir::Expr) -> TokenStream {
/// Generates a Rust expression from the default value that implemenets
/// `serde::de::IntoDeserializer`.
fn default_value_to_deserializable_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);
let items = arr.iter().map(default_value_to_deserializable_expr);
quote! { confique::internal::ArrayIntoDeserializer([ #(#items),* ]) }
},
}
}
/// Returns tokens defining the visibility of the items in the inner module.
fn inner_visibility(outer: &syn::Visibility, span: Span) -> TokenStream {
match outer {
// These visibilities can be used as they are. No adjustment needed.

View File

@@ -1,9 +1,5 @@
//! Definition of the intermediate representation.
use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned, punctuated::Punctuated};
use crate::util::{is_option, unwrap_option};
/// The parsed input to the `gen_config` macro.
pub(crate) struct Input {
@@ -72,295 +68,3 @@ pub(crate) enum Expr {
Bool(syn::LitBool),
Array(Vec<Expr>),
}
impl Input {
pub(crate) fn from_ast(mut input: syn::DeriveInput) -> Result<Self, Error> {
let fields = match input.data {
syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(f), .. }) => f,
_ => return Err(Error::new(
input.span(),
"`confique::Config` can only be derive for structs with named fields",
)),
};
let doc = extract_doc(&mut input.attrs);
let fields = fields.named.into_iter()
.map(Field::from_ast)
.collect::<Result<Vec<_>, _>>()?;
Ok(Self {
doc,
visibility: input.vis,
name: input.ident,
fields,
})
}
}
impl Field {
fn from_ast(mut field: syn::Field) -> Result<Self, Error> {
let doc = extract_doc(&mut field.attrs);
let attrs = extract_internal_attrs(&mut field.attrs)?;
// TODO: check no other attributes are here
let kind = if attrs.nested {
if is_option(&field.ty) {
return Err(Error::new(
field.ident.span(),
"nested configurations cannot be optional (type `Option<_>`)",
));
}
if attrs.default.is_some() {
return Err(Error::new(
field.ident.span(),
"cannot specify `nested` and `default` attributes at the same time",
));
}
if attrs.env.is_some() {
return Err(Error::new(
field.ident.span(),
"cannot specify `nested` and `env` attributes at the same time",
));
}
if attrs.deserialize_with.is_some() {
return Err(Error::new(
field.ident.span(),
"cannot specify `nested` and `deserialize_with` attributes at the same time",
));
}
FieldKind::Nested { ty: field.ty }
} else {
match unwrap_option(&field.ty) {
None => FieldKind::Leaf {
env: attrs.env,
deserialize_with: attrs.deserialize_with,
kind: LeafKind::Required {
default: attrs.default,
ty: field.ty,
},
},
Some(inner) => {
if attrs.default.is_some() {
return Err(Error::new(
field.ident.span(),
"optional fields (type `Option<_>`) cannot have default \
values (`#[config(default = ...)]`)",
));
}
FieldKind::Leaf {
env: attrs.env,
deserialize_with: attrs.deserialize_with,
kind: LeafKind::Optional {
inner_ty: inner.clone(),
},
}
}
}
};
Ok(Self {
doc,
name: field.ident.expect("bug: expected named field"),
kind,
})
}
pub(crate) fn is_leaf(&self) -> bool {
matches!(self.kind, FieldKind::Leaf { .. })
}
}
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";
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)),
}
}
}
}
/// Extracts all doc string attributes from the list and return them as list of
/// strings (in order).
fn extract_doc(attrs: &mut Vec<syn::Attribute>) -> Vec<String> {
extract_attrs(attrs, |attr| {
match attr.parse_meta().ok()? {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(s),
path,
..
}) if path.is_ident("doc") => Some(s.value()),
_ => None,
}
})
}
fn extract_attrs<P, O>(attrs: &mut Vec<syn::Attribute>, mut pred: P) -> Vec<O>
where
P: FnMut(&syn::Attribute) -> Option<O>,
{
// TODO: use `Vec::drain_filter` once stabilized. The current impl is O(n²).
let mut i = 0;
let mut out = Vec::new();
while i < attrs.len() {
match pred(&attrs[i]) {
Some(v) => {
out.push(v);
attrs.remove(i);
}
None => i += 1,
}
}
out
}
fn extract_internal_attrs(
attrs: &mut Vec<syn::Attribute>,
) -> Result<InternalAttrs, Error> {
let internal_attrs = extract_attrs(attrs, |attr| {
if attr.path.is_ident("config") {
// TODO: clone not necessary once we use drain_filter
Some(attr.clone())
} else {
None
}
});
let mut out = InternalAttrs::default();
for attr in internal_attrs {
type AttrList = Punctuated<InternalAttr, Token![,]>;
let parsed_list = attr.parse_args_with(AttrList::parse_terminated)?;
for parsed in parsed_list {
let keyword = parsed.keyword();
macro_rules! duplicate_if {
($cond:expr) => {
if $cond {
let msg = format!("duplicate '{keyword}' confique attribute");
return Err(Error::new(attr.tokens.span(), msg));
}
};
}
match parsed {
InternalAttr::Default(expr) => {
duplicate_if!(out.default.is_some());
out.default = Some(expr);
}
InternalAttr::Nested => {
duplicate_if!(out.nested);
out.nested = true;
}
InternalAttr::Env(key) => {
duplicate_if!(out.env.is_some());
out.env = Some(key);
}
InternalAttr::DeserializeWith(path) => {
duplicate_if!(out.deserialize_with.is_some());
out.deserialize_with = Some(path);
}
}
}
}
Ok(out)
}
#[derive(Default)]
struct InternalAttrs {
nested: bool,
default: Option<Expr>,
env: Option<String>,
deserialize_with: Option<syn::Path>,
}
enum InternalAttr {
Nested,
Default(Expr),
Env(String),
DeserializeWith(syn::Path),
}
impl InternalAttr {
fn keyword(&self) -> &'static str {
match self {
Self::Nested => "nested",
Self::Default(_) => "default",
Self::Env(_) => "env",
Self::DeserializeWith(_) => "deserialize_with",
}
}
}
impl Parse for InternalAttr {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let ident: syn::Ident = input.parse()?;
match &*ident.to_string() {
"nested" => {
assert_empty_or_comma(input)?;
Ok(Self::Nested)
}
"default" => {
let _: Token![=] = input.parse()?;
let expr: Expr = input.parse()?;
assert_empty_or_comma(input)?;
Ok(Self::Default(expr))
}
"env" => {
let _: Token![=] = input.parse()?;
let key: syn::LitStr = input.parse()?;
assert_empty_or_comma(input)?;
let value = key.value();
if value.contains('=') || value.contains('\0') {
Err(syn::Error::new(
key.span(),
"environment variable key must not contain '=' or null bytes",
))
} else {
Ok(Self::Env(value))
}
}
"deserialize_with" => {
let _: Token![=] = input.parse()?;
let path: syn::Path = input.parse()?;
assert_empty_or_comma(input)?;
Ok(Self::DeserializeWith(path))
}
_ => Err(syn::Error::new(ident.span(), "unknown confique attribute")),
}
}
}
fn assert_empty_or_comma(input: ParseStream) -> Result<(), Error> {
if input.is_empty() || input.peek(Token![,]) {
Ok(())
} else {
Err(Error::new(input.span(), "unexpected tokens, expected no more tokens in this context"))
}
}

View File

@@ -3,6 +3,7 @@ use proc_macro::TokenStream as TokenStream1;
mod gen;
mod ir;
mod parse;
mod util;

299
macro/src/parse.rs Normal file
View File

@@ -0,0 +1,299 @@
use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned, punctuated::Punctuated};
use crate::{
ir::{Input, Field, FieldKind, LeafKind, Expr},
util::{unwrap_option, is_option},
};
impl Input {
pub(crate) fn from_ast(mut input: syn::DeriveInput) -> Result<Self, Error> {
let fields = match input.data {
syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(f), .. }) => f,
_ => return Err(Error::new(
input.span(),
"`confique::Config` can only be derive for structs with named fields",
)),
};
let doc = extract_doc(&mut input.attrs);
let fields = fields.named.into_iter()
.map(Field::from_ast)
.collect::<Result<Vec<_>, _>>()?;
Ok(Self {
doc,
visibility: input.vis,
name: input.ident,
fields,
})
}
}
impl Field {
fn from_ast(mut field: syn::Field) -> Result<Self, Error> {
let doc = extract_doc(&mut field.attrs);
let attrs = extract_internal_attrs(&mut field.attrs)?;
// TODO: check no other attributes are here
let kind = if attrs.nested {
if is_option(&field.ty) {
return Err(Error::new(
field.ident.span(),
"nested configurations cannot be optional (type `Option<_>`)",
));
}
if attrs.default.is_some() {
return Err(Error::new(
field.ident.span(),
"cannot specify `nested` and `default` attributes at the same time",
));
}
if attrs.env.is_some() {
return Err(Error::new(
field.ident.span(),
"cannot specify `nested` and `env` attributes at the same time",
));
}
if attrs.deserialize_with.is_some() {
return Err(Error::new(
field.ident.span(),
"cannot specify `nested` and `deserialize_with` attributes at the same time",
));
}
FieldKind::Nested { ty: field.ty }
} else {
match unwrap_option(&field.ty) {
None => FieldKind::Leaf {
env: attrs.env,
deserialize_with: attrs.deserialize_with,
kind: LeafKind::Required {
default: attrs.default,
ty: field.ty,
},
},
Some(inner) => {
if attrs.default.is_some() {
return Err(Error::new(
field.ident.span(),
"optional fields (type `Option<_>`) cannot have default \
values (`#[config(default = ...)]`)",
));
}
FieldKind::Leaf {
env: attrs.env,
deserialize_with: attrs.deserialize_with,
kind: LeafKind::Optional {
inner_ty: inner.clone(),
},
}
}
}
};
Ok(Self {
doc,
name: field.ident.expect("bug: expected named field"),
kind,
})
}
pub(crate) fn is_leaf(&self) -> bool {
matches!(self.kind, FieldKind::Leaf { .. })
}
}
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";
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)),
}
}
}
}
/// Extracts all doc string attributes from the list and return them as list of
/// strings (in order).
fn extract_doc(attrs: &mut Vec<syn::Attribute>) -> Vec<String> {
extract_attrs(attrs, |attr| {
match attr.parse_meta().ok()? {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(s),
path,
..
}) if path.is_ident("doc") => Some(s.value()),
_ => None,
}
})
}
fn extract_attrs<P, O>(attrs: &mut Vec<syn::Attribute>, mut pred: P) -> Vec<O>
where
P: FnMut(&syn::Attribute) -> Option<O>,
{
// TODO: use `Vec::drain_filter` once stabilized. The current impl is O(n²).
let mut i = 0;
let mut out = Vec::new();
while i < attrs.len() {
match pred(&attrs[i]) {
Some(v) => {
out.push(v);
attrs.remove(i);
}
None => i += 1,
}
}
out
}
fn extract_internal_attrs(
attrs: &mut Vec<syn::Attribute>,
) -> Result<InternalAttrs, Error> {
let internal_attrs = extract_attrs(attrs, |attr| {
if attr.path.is_ident("config") {
// TODO: clone not necessary once we use drain_filter
Some(attr.clone())
} else {
None
}
});
let mut out = InternalAttrs::default();
for attr in internal_attrs {
type AttrList = Punctuated<InternalAttr, Token![,]>;
let parsed_list = attr.parse_args_with(AttrList::parse_terminated)?;
for parsed in parsed_list {
let keyword = parsed.keyword();
macro_rules! duplicate_if {
($cond:expr) => {
if $cond {
let msg = format!("duplicate '{keyword}' confique attribute");
return Err(Error::new(attr.tokens.span(), msg));
}
};
}
match parsed {
InternalAttr::Default(expr) => {
duplicate_if!(out.default.is_some());
out.default = Some(expr);
}
InternalAttr::Nested => {
duplicate_if!(out.nested);
out.nested = true;
}
InternalAttr::Env(key) => {
duplicate_if!(out.env.is_some());
out.env = Some(key);
}
InternalAttr::DeserializeWith(path) => {
duplicate_if!(out.deserialize_with.is_some());
out.deserialize_with = Some(path);
}
}
}
}
Ok(out)
}
#[derive(Default)]
struct InternalAttrs {
nested: bool,
default: Option<Expr>,
env: Option<String>,
deserialize_with: Option<syn::Path>,
}
enum InternalAttr {
Nested,
Default(Expr),
Env(String),
DeserializeWith(syn::Path),
}
impl InternalAttr {
fn keyword(&self) -> &'static str {
match self {
Self::Nested => "nested",
Self::Default(_) => "default",
Self::Env(_) => "env",
Self::DeserializeWith(_) => "deserialize_with",
}
}
}
impl Parse for InternalAttr {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let ident: syn::Ident = input.parse()?;
match &*ident.to_string() {
"nested" => {
assert_empty_or_comma(input)?;
Ok(Self::Nested)
}
"default" => {
let _: Token![=] = input.parse()?;
let expr: Expr = input.parse()?;
assert_empty_or_comma(input)?;
Ok(Self::Default(expr))
}
"env" => {
let _: Token![=] = input.parse()?;
let key: syn::LitStr = input.parse()?;
assert_empty_or_comma(input)?;
let value = key.value();
if value.contains('=') || value.contains('\0') {
Err(syn::Error::new(
key.span(),
"environment variable key must not contain '=' or null bytes",
))
} else {
Ok(Self::Env(value))
}
}
"deserialize_with" => {
let _: Token![=] = input.parse()?;
let path: syn::Path = input.parse()?;
assert_empty_or_comma(input)?;
Ok(Self::DeserializeWith(path))
}
_ => Err(syn::Error::new(ident.span(), "unknown confique attribute")),
}
}
}
fn assert_empty_or_comma(input: ParseStream) -> Result<(), Error> {
if input.is_empty() || input.peek(Token![,]) {
Ok(())
} else {
Err(Error::new(input.span(), "unexpected tokens, expected no more tokens in this context"))
}
}