mirror of
https://github.com/OMGeeky/confique.git
synced 2026-01-17 00:47:19 +01:00
Move code around in macro crate
This commit is contained in:
193
macro/src/gen/meta.rs
Normal file
193
macro/src/gen/meta.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
296
macro/src/ir.rs
296
macro/src/ir.rs
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
299
macro/src/parse.rs
Normal 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"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user