From 651a06b25298ce2345e57fa159efe62a2c42a741 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 29 Apr 2021 23:18:22 +0200 Subject: [PATCH] Add `typename` attribute and refactor a bunch This commit should have been two, I know. I feel bad about it. --- examples/simple.rs | 2 +- macro/src/ast.rs | 57 ++++--- macro/src/gen.rs | 395 ++++++++++++++++++++++++++------------------- macro/src/parse.rs | 48 ++++-- src/example.rs | 2 + 5 files changed, 306 insertions(+), 198 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index ae4adfa..8b34fad 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -3,7 +3,7 @@ mod config { use std::path::PathBuf; confique::config! { - #[derive(Clone)] + #[derive(Debug, Clone)] log: { /// Determines how many messages are logged. Log messages below /// this level are not emitted. Possible values: "trace", "debug", diff --git a/macro/src/ast.rs b/macro/src/ast.rs index 9b8dc0f..0b944e6 100644 --- a/macro/src/ast.rs +++ b/macro/src/ast.rs @@ -1,6 +1,6 @@ //! Definition of the intermediate representation or AST. -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; /// The parsed input to the `gen_config` macro. @@ -12,21 +12,30 @@ pub(crate) struct Input { /// One node in the tree of the configuration format. Can either be a leaf node /// (a string, int, float or bool value) or an internal node that contains /// children. -pub(crate) enum Node { - Internal { - doc: Vec, - /// Attributes that are used as specified and not interpreted by us. - attrs: Vec, - name: syn::Ident, - children: Vec, - }, - Leaf { - doc: Vec, - name: syn::Ident, - ty: syn::Type, - default: Option, - example: Option, - }, +pub(crate) enum NodeKind { + Obj(Obj), + Leaf(Leaf), +} + +pub(crate) struct Node { + /// The doc string lines. + pub(crate) doc: Vec, + /// Attributes that are used as specified and not interpreted by us. + pub(crate) attrs: Vec, + + pub(crate) name: syn::Ident, + pub(crate) kind: NodeKind, +} + +pub(crate) struct Obj { + pub(crate) typename: Option, + pub(crate) children: Vec, +} + +pub(crate) struct Leaf { + pub(crate) ty: syn::Type, + pub(crate) default: Option, + pub(crate) example: Option, } /// The kinds of expressions (just literals) we allow for default or example @@ -39,10 +48,18 @@ pub(crate) enum Expr { } impl Node { - pub(crate) fn name(&self) -> &syn::Ident { - match self { - Self::Internal { name, .. } => name, - Self::Leaf { name, .. } => name, + pub(crate) fn typename(&self) -> Option { + match &self.kind { + NodeKind::Obj(Obj { typename, .. }) => { + use heck::CamelCase; + + let out = typename.clone().unwrap_or_else(|| { + Ident::new(&self.name.to_string().to_camel_case(), self.name.span()) + }); + + Some(out) + } + NodeKind::Leaf(_) => None, } } } diff --git a/macro/src/gen.rs b/macro/src/gen.rs index 38a9fd3..b0ff03a 100644 --- a/macro/src/gen.rs +++ b/macro/src/gen.rs @@ -3,102 +3,239 @@ use quote::{quote, ToTokens}; use syn::Ident; use std::fmt::{self, Write}; -use crate::ast::{Expr, Input, Node}; +use crate::ast::{Expr, Input, Leaf, Node, NodeKind, Obj}; pub(crate) fn gen(input: Input) -> TokenStream { let visibility = input.visibility.clone().unwrap_or(quote! { pub(crate) }); let toml = gen_toml(&input); - let root_mod = gen_root_mod(&input, &visibility); - let raw_mod = gen_raw_mod(&input, &visibility); + let types = gen_types(&input, &visibility); quote! { #visibility const TOML_TEMPLATE: &str = #toml; - #root_mod - #raw_mod + #types } } -fn gen_raw_mod(input: &Input, visibility: &TokenStream) -> TokenStream { - let mut contents = TokenStream::new(); +/// Generates the struct fields for both, the raw struct and the main struct. +fn gen_struct_fields(children: &[Node], visibility: &TokenStream) -> (TokenStream, TokenStream) { + let mut raw_fields = TokenStream::new(); + let mut main_fields = TokenStream::new(); + + for child in children { + let name = &child.name; + let doc = &child.doc; + + match &child.kind { + NodeKind::Obj(_) => { + let child_typename = child.typename().unwrap(); + let default_path = format!("{}::empty", child_typename); + raw_fields.extend(quote! { + #[serde(default = #default_path)] + #visibility #name: #child_typename, + }); + main_fields.extend(quote! { + #visibility #name: #child_typename, + }); + } + NodeKind::Leaf(Leaf { ty, .. }) => { + let inner = as_option(&ty).unwrap_or(&ty); + raw_fields.extend(quote! { + #visibility #name: Option<#inner>, + }); + main_fields.extend(quote! { + #( #[doc = #doc] )* + #visibility #name: #ty, + }); + } + } + } + + (raw_fields, main_fields) +} + +/// Generates the definition for `default_values`, a function associated with raw types. +fn gen_raw_default_constructor( + children: &[Node], + path: &[String], + visibility: &TokenStream, +) -> TokenStream { + let fields = collect_tokens(children, |node| { + let name = &node.name; + match &node.kind { + NodeKind::Leaf(Leaf { default: None, .. }) => quote! { #name: None, }, + NodeKind::Leaf(Leaf { default: Some(expr), ty, .. }) => { + // TODO: we can specialize this for certain types such that we + // don't have to invoke serde. + let inner_type = as_option(ty).unwrap_or(ty); + let path = append_path(path, name); + let msg = format!( + "default configuration value for '{}' cannot be deserialized as '{}'", + path, + inner_type.to_token_stream(), + ); + + quote! { + #name: Some({ + let result: Result<_, confique::serde::de::value::Error> + = Deserialize::deserialize(#expr.into_deserializer()); + result.expect(#msg) + }), + } + }, + NodeKind::Obj(_) => { + let child_typename = node.typename().unwrap(); + quote! { + #name: #child_typename::default_values(), + } + } + } + }); + + quote! { + /// Returns an instance of `Self` that contains the specified default + /// configuration values. All fields that don't have a default value + /// specified are `None`. + #visibility fn default_values() -> Self { + Self { #fields } + } + } +} + +/// Generates the definition for `empty`, a function associated with raw types. +fn gen_raw_empty_constructor(children: &[Node], visibility: &TokenStream) -> TokenStream { + let fields = collect_tokens(children, |node| { + let name = &node.name; + match &node.kind { + NodeKind::Leaf(_) => quote! { #name: None, }, + NodeKind::Obj(_) => { + let child_typename = node.typename().unwrap(); + quote! { + #name: #child_typename::empty(), + } + } + } + }); + + quote! { + /// Returns an instance of `Self` where all values are `None`. + #visibility fn empty() -> Self { + Self { #fields } + } + } +} + +/// Generates the definition of the `overwrite_with` method on raw types. +fn gen_raw_overwrite_with_method(children: &[Node], visibility: &TokenStream) -> TokenStream { + let fields = collect_tokens(children, |Node { name, kind, .. }| { + match kind { + NodeKind::Leaf(_) => quote! { #name: other.#name.or(self.#name), }, + NodeKind::Obj(_) => quote! { #name: self.#name.overwrite_with(other.#name), }, + } + }); + + quote! { + // TODO: Find better name + #visibility fn overwrite_with(self, other: Self) -> Self { + Self { #fields } + } + } +} + +/// Generates the impl to convert from a raw type to a main type. +fn gen_try_from_impl(typename: &Ident, children: &[Node], path: &[String]) -> TokenStream { + let fields = collect_tokens(children, |Node { name, kind, .. }| { + match kind { + NodeKind::Leaf(Leaf { ty, .. }) => { + if as_option(ty).is_some() { + // If this value is optional, we just move it as it can never fail. + quote! { #name: src.#name, } + } else { + // Otherwise, we return an error if the value hasn't been specified. + let path = append_path(path, name); + + quote! { + #name: src.#name.ok_or(confique::TryFromError { path: #path })?, + } + } + }, + NodeKind::Obj(_) => quote! { + #name: std::convert::TryFrom::try_from(src.#name)?, + }, + } + }); + + quote! { + impl std::convert::TryFrom for #typename { + type Error = confique::TryFromError; + fn try_from(src: raw::#typename) -> Result { + Ok(Self { + #fields + }) + } + } + } +} + +fn append_path(path: &[String], name: &Ident) -> String { + if path.is_empty() { + name.to_string() + } else { + format!("{}.{}", path.join("."), name) + } +} + +fn gen_types(input: &Input, visibility: &TokenStream) -> TokenStream { + let mut raw_types = TokenStream::new(); + let mut main_types = TokenStream::new(); + visit(input, |node, path| { - if let Node::Internal { name, children, .. } = node { - let type_name = to_camel_case(name); + if let NodeKind::Obj(Obj { children, .. }) = &node.kind { + let typename = node.typename().unwrap(); - let raw_fields = collect_tokens(children, |node| { - match node { - Node::Leaf { name, ty, .. } => { - let inner = as_option(&ty).unwrap_or(&ty); - quote! { #visibility #name: Option<#inner>, } - }, - Node::Internal { name, .. } => { - let child_type_name = to_camel_case(name); - quote! { - #[serde(default)] - #visibility #name: #child_type_name, - } - }, - } - }); + let (raw_fields, main_fields) = gen_struct_fields(&children, visibility); + let raw_default_constructor = gen_raw_default_constructor(&children, path, visibility); + let raw_empty_constructor = gen_raw_empty_constructor(&children, visibility); + let overwrite_with_method = gen_raw_overwrite_with_method(&children, visibility); + let try_from_impl = gen_try_from_impl(&typename, &children, path); - let default_fields = collect_tokens(children, |node| { - match node { - Node::Leaf { name, default: None, .. } => quote! { #name: None, }, - Node::Leaf { name, default: Some(expr), ty, .. } => { - let inner_type = as_option(ty).unwrap_or(ty); - let path = format!("{}.{}", path.join("."), name); - let msg = format!( - "default configuration value for '{}' cannot be deserialized as '{}'", - path, - inner_type.to_token_stream(), - ); - - quote! { - #name: Some({ - let result: Result<_, confique::serde::de::value::Error> - = Deserialize::deserialize(#expr.into_deserializer()); - result.expect(#msg) - }), - } - }, - Node::Internal { name, .. } => { - let child_type_name = to_camel_case(name); - quote! { - #name: #child_type_name::default_values(), - } - } - } - }); - - let overwrite_with_fields = collect_tokens(children, |node| { - match node { - Node::Leaf { name, .. } => quote! { - #name: other.#name.or(self.#name), - }, - Node::Internal { name, .. } => quote! { - #name: self.#name.overwrite_with(other.#name), - } - } - }); - - contents.extend(quote! { - #[derive(Debug, Default, confique::serde::Deserialize)] - #[serde(deny_unknown_fields)] - #visibility struct #type_name { + // Raw type definition + raw_types.extend(quote! { + #[derive(Debug, Deserialize)] + #visibility struct #typename { #raw_fields } - impl #type_name { - #visibility fn default_values() -> Self { - Self { #default_fields } - } - - #visibility fn overwrite_with(self, other: Self) -> Self { - Self { #overwrite_with_fields } - } + impl #typename { + #raw_default_constructor + #raw_empty_constructor + #overwrite_with_method } }); + + // Main type definition + let doc = &node.doc; + let attrs = &node.attrs; + + // We add some derives if the user has not specified any + let derive = if attrs.iter().any(|attr| attr.path.is_ident("derive")) { + quote! {} + } else { + quote! { #[derive(Debug)] } + }; + + main_types.extend(quote! { + #( #[doc = #doc] )* + #( #attrs )* + #derive + + #visibility struct #typename { + #main_fields + } + + #try_from_impl + }); } }); @@ -110,94 +247,24 @@ fn gen_raw_mod(input: &Input, visibility: &TokenStream) -> TokenStream { /// "layers" of configuration sources. Imagine that the three layers: /// environment variables, a TOML file and the fixed default values. The /// only thing that matters is that required values are present after - /// merging all sources, but each individual source can be missing - /// required values. + /// merging all sources, but each individual is allowed to lack required + /// values. /// - /// These types implement `serde::Deserialize`. + /// These types implement `serde::Deserialize` and `Debug`. #visibility mod raw { + // We have to add this blanket use to be able to refer to all the + // types the user referred to. use super::*; + use confique::serde::{Deserialize, de::IntoDeserializer}; - #contents + #raw_types } + + #main_types } } -fn gen_root_mod(input: &Input, visibility: &TokenStream) -> TokenStream { - let mut out = TokenStream::new(); - visit(input, |node, path| { - if let Node::Internal { name, doc, attrs, children } = node { - let type_name = to_camel_case(name); - - let user_fields = collect_tokens(children, |node| { - match node { - Node::Leaf { name, doc, ty, .. } => quote! { - #( #[doc = #doc] )* - #visibility #name: #ty, - }, - Node::Internal { name, .. } => { - let child_type_name = to_camel_case(name); - quote! { - #visibility #name: #child_type_name, - } - }, - } - }); - - let try_from_fields = collect_tokens(children, |node| { - match node { - Node::Leaf { name, ty, .. } => { - if as_option(ty).is_some() { - // If this value is optional, we just move it as it can never fail. - quote! { #name: src.#name, } - } else { - // Otherwise, we return an error if the value hasn't been specified. - let path = match path.is_empty() { - true => name.to_string(), - false => format!("{}.{}", path.join("."), name), - }; - - quote! { - #name: src.#name.ok_or(confique::TryFromError { path: #path })?, - } - } - }, - Node::Internal { name, .. } => quote! { - #name: std::convert::TryFrom::try_from(src.#name)?, - }, - } - }); - - // We add some derives if the user has not specified any - let derive = if attrs.iter().any(|attr| attr.path.is_ident("derive")) { - quote! {} - } else { - quote! { #[derive(Debug)] } - }; - - out.extend(quote! { - #( #[doc = #doc] )* - #( #attrs )* - #derive - - #visibility struct #type_name { - #user_fields - } - - impl std::convert::TryFrom for #type_name { - type Error = confique::TryFromError; - fn try_from(src: raw::#type_name) -> Result { - Ok(Self { - #try_from_fields - }) - } - } - }); - } - }); - - out -} /// Generates the TOML template file. fn gen_toml(input: &Input) -> String { @@ -220,9 +287,9 @@ fn gen_toml(input: &Input) -> String { let mut out = String::new(); - visit(input, |node, path| { - match node { - Node::Internal { doc, .. } => { + visit(input, |Node { name, doc, kind, .. }, path| { + match kind { + NodeKind::Obj(_) => { write_doc(&mut out, doc); // If a new subsection starts, we always print the header, even if not @@ -234,7 +301,7 @@ fn gen_toml(input: &Input) -> String { } } - Node::Leaf { doc, name, ty, default, example } => { + NodeKind::Leaf(Leaf { ty, default, example }) => { write_doc(&mut out, doc); // Add note about default value or the value being required. @@ -284,10 +351,10 @@ where while let Some((node, path)) = stack.pop() { visitor(&node, &path); - if let Node::Internal { children, .. } = node { + if let NodeKind::Obj(Obj { children, .. }) = &node.kind { for child in children.iter().rev() { let mut child_path = path.clone(); - child_path.push(child.name().to_string()); + child_path.push(child.name.to_string()); stack.push((child, child_path)); } } @@ -303,12 +370,6 @@ fn collect_tokens( it.into_iter().map(f).collect() } -fn to_camel_case(ident: &Ident) -> Ident { - use heck::CamelCase; - - Ident::new(&ident.to_string().to_camel_case(), ident.span()) -} - /// Checks if the given type is an `Option` and if so, return the inner type. /// /// Note: this function clearly shows one of the major shortcomings of proc diff --git a/macro/src/parse.rs b/macro/src/parse.rs index bee2bd9..bd2afa6 100644 --- a/macro/src/parse.rs +++ b/macro/src/parse.rs @@ -6,7 +6,7 @@ use syn::{ spanned::Spanned, }; -use crate::ast::{Expr, Input, Node}; +use crate::ast::{Expr, Input, Leaf, Node, NodeKind, Obj}; @@ -14,6 +14,8 @@ impl Parse for Input { fn parse(input: ParseStream) -> Result { let mut outer_attrs = input.call(syn::Attribute::parse_inner)?; let doc = extract_doc(&mut outer_attrs)?; + let visibility = extract_visibility(&mut outer_attrs)?; + let typename = extract_typename(&mut outer_attrs)?; // Extract attributes that we will just forward/emit verbatim. Well, not // completely verbatim: we have to change the type to outer attribute. @@ -22,19 +24,18 @@ impl Parse for Input { a.style = syn::AttrStyle::Outer; } - // `#![visibility = "..."]` - let visibility = extract_single_name_value_attr("visibility", &mut outer_attrs)? - .map(|v| Ok::<_, syn::Error>(assert_string_lit(v)?.parse::()?)) - .transpose()?; assert_no_extra_attrs(&outer_attrs)?; let children = input.call(>::parse_terminated)?; - let root = Node::Internal { + let root = Node { doc, attrs: forwarded_attrs, name: Ident::new("config", Span::call_site()), - children: children.into_iter().collect(), + kind: NodeKind::Obj(Obj { + typename, + children: children.into_iter().collect(), + }), }; Ok(Self { root, visibility }) @@ -53,17 +54,21 @@ impl Parse for Node { let out = if input.lookahead1().peek(syn::token::Brace) { // --- A nested Internal --- + let typename = extract_typename(&mut attrs)?; let forwarded_attrs = extract_attrs(&["derive"], &mut attrs); let inner; syn::braced!(inner in input); let fields = inner.call(>::parse_terminated)?; - Self::Internal { + Self { doc, attrs: forwarded_attrs, name, - children: fields.into_iter().collect(), + kind: NodeKind::Obj(Obj { + typename, + children: fields.into_iter().collect(), + }), } } else { // --- A single value --- @@ -93,7 +98,12 @@ impl Parse for Node { return Err(Error::new(name.span(), msg)); } - Self::Leaf { doc, name, ty, default, example } + Self { + doc, + attrs: vec![], + name, + kind: NodeKind::Leaf(Leaf { ty, default, example }), + } }; assert_no_extra_attrs(&attrs)?; @@ -202,3 +212,21 @@ fn assert_string_lit(lit: syn::Lit) -> Result { _ => Err(Error::new(lit.span(), "expected string literal")), } } + +/// `#[visibility = "..."]` +fn extract_visibility(attrs: &mut Vec) -> Result, Error> { + extract_single_name_value_attr("visibility", attrs)? + .map(|v| Ok::<_, syn::Error>(assert_string_lit(v)?.parse::()?)) + .transpose() +} + +/// `#[typename = "..."]` +fn extract_typename(attrs: &mut Vec) -> Result, Error> { + extract_single_name_value_attr("typename", attrs)? + .map(|lit| { + let span = lit.span(); + let s = assert_string_lit(lit)?; + Ok(Ident::new(&s, span)) + }) + .transpose() +} diff --git a/src/example.rs b/src/example.rs index abc4715..849daef 100644 --- a/src/example.rs +++ b/src/example.rs @@ -28,6 +28,8 @@ crate::config! { /// How often to reattempt reaching the DNS server. retry_attempts: u32 = 27, }, + + #[typename = "Logger"] log: { /// Sets the log level. Possible values: "trace", "debug", "info", /// "warn", "error" and "off".