Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ toml = { version = "0.8", optional = true }

[dev-dependencies]
pretty_assertions = "1.2.1"
derive_more = { version = "2.0.1", features = ["debug", "display", "deref"] }


[package.metadata.docs.rs]
Expand Down
5 changes: 4 additions & 1 deletion macro/src/gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ fn gen_parts_for_field(f: &ir::Field, input: &ir::Input, parts: &mut Parts) {
FieldKind::Nested { ty } => {
let ty_span = ty.span();
let field_ty = quote_spanned! {ty_span=> <#ty as confique::Config>::Partial };
let partial_attrs = &f.partial_attrs;
parts.struct_fields.push(quote! {
#( #[ #partial_attrs ])*
#[serde(default = "confique::Partial::empty")]
#field_visibility #field_name: #field_ty,
});
Expand Down Expand Up @@ -315,7 +317,8 @@ fn gen_parts_for_field(f: &ir::Field, input: &ir::Input, parts: &mut Parts) {
let main = quote_spanned! {field_name.span()=>
#field_visibility #field_name: std::option::Option<#inner_ty>,
};
quote! { #attr #main }
let partial_attrs = &f.partial_attrs;
quote! { #attr #( #[ #partial_attrs ])* #main }
});


Expand Down
1 change: 1 addition & 0 deletions macro/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub(crate) struct Field {
pub(crate) doc: Vec<String>,
pub(crate) name: syn::Ident,
pub(crate) kind: FieldKind,
pub(crate) partial_attrs: Vec<TokenStream>

// TODO:
// - serde attributes
Expand Down
37 changes: 29 additions & 8 deletions macro/src/parse.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use proc_macro2::{Delimiter, Group, Ident, TokenStream, TokenTree};
use syn::{Error, Token, parse::{Parse, ParseStream}, spanned::Spanned, punctuated::Punctuated};
use syn::{
parse::{Parse, ParseBuffer, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
Error, Token,
};

use crate::{
ir::{Expr, Field, FieldKind, FieldValidator, Input, LeafKind, MapEntry, MapKey},
Expand Down Expand Up @@ -97,13 +102,8 @@ impl Parse for StructAttr {
let ident: Ident = input.parse()?;
match &*ident.to_string() {
"partial_attr" => {
let g: Group = input.parse()?;
if g.delimiter() != Delimiter::Parenthesis {
return Err(Error::new_spanned(g,
"expected `(...)` but found different delimiter"));
}
assert_empty_or_comma(&input)?;
Ok(Self::PartialAttrs(g.stream()))
let partial_stream = parse_partial_attr(input)?;
Ok(Self::PartialAttrs(partial_stream))
}
"validate" => parse_eq_value(input).map(Self::Validate),
_ => Err(syn::Error::new(ident.span(), "unknown confique attribute")),
Expand Down Expand Up @@ -172,6 +172,7 @@ impl Field {
doc,
name: field.ident.expect("bug: expected named field"),
kind,
partial_attrs: attrs.partial_attrs
})
}
}
Expand All @@ -187,6 +188,7 @@ struct FieldAttrs {
deserialize_with: Option<syn::Path>,
parse_env: Option<syn::Path>,
validate: Option<FieldValidator>,
partial_attrs: Vec<TokenStream>,
}

enum FieldAttr {
Expand All @@ -196,6 +198,7 @@ enum FieldAttr {
DeserializeWith(syn::Path),
ParseEnv(syn::Path),
Validate(FieldValidator),
PartialAttr(TokenStream),
}

impl FieldAttrs {
Expand Down Expand Up @@ -244,6 +247,9 @@ impl FieldAttrs {
duplicate_if!(out.validate.is_some());
out.validate = Some(path);
}
FieldAttr::PartialAttr(partial_attr) => {
out.partial_attrs.push(partial_attr);
}
}
}
}
Expand All @@ -261,6 +267,7 @@ impl FieldAttr {
Self::ParseEnv(_) => "parse_env",
Self::DeserializeWith(_) => "deserialize_with",
Self::Validate(_) => "validate",
Self::PartialAttr(_) => "partial_attr",
}
}
}
Expand Down Expand Up @@ -329,6 +336,10 @@ impl Parse for FieldAttr {
))
}
}
"partial_attr" => {
let partial_stream = parse_partial_attr(input)?;
Ok(Self::PartialAttr(partial_stream))
}

_ => Err(syn::Error::new(ident.span(), "unknown confique attribute")),
}
Expand Down Expand Up @@ -397,6 +408,16 @@ impl Parse for MapKey {

// ===== Util =====================================================================

fn parse_partial_attr(input: &ParseBuffer<'_>) -> syn::Result<TokenStream> {
let g: Group = input.parse()?;
if g.delimiter() != Delimiter::Parenthesis {
return Err(Error::new_spanned(g,
"expected `(...)` but found different delimiter"));
}
assert_empty_or_comma(&input)?;
Ok(g.stream())
}

fn assert_empty_or_comma(input: ParseStream) -> Result<(), Error> {
if input.is_empty() || input.peek(Token![,]) {
Ok(())
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,8 @@ pub use crate::{
/// For example, `#[config(partial_attr(derive(Clone)))]` can be used to make
/// the partial type implement `Clone`.
///
/// This attribute can also be applied to struct fields.
///
///
/// # What the macro generates
///
Expand Down
61 changes: 61 additions & 0 deletions tests/partial_props.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use confique::Config;
use pretty_assertions::assert_eq;

#[test]
fn partial_props() {
#[allow(dead_code)]
#[derive(Config)]
#[config(
partial_attr(derive(derive_more::Debug, derive_more::Display)),
partial_attr(display("{}", self.bar.unwrap_or_default())),
)]
struct Foo {
#[config(default = 1, partial_attr(debug("test {bar:?}")))]
bar: u32,
}

type PartialFoo = <Foo as Config>::Partial;

let partial_foo = PartialFoo { bar: Some(1) };
assert_eq!(
format!("{partial_foo:?}"),
"PartialFoo { bar: test Some(1) }",
);

assert_eq!(format!("{partial_foo}"), "1");
}

#[test]
fn partial_props_nested() {
Comment thread
LukasKalbertodt marked this conversation as resolved.
mod foo {
use confique::Config;

#[allow(dead_code)]
#[derive(Config)]
#[config(partial_attr(derive(derive_more::Debug, derive_more::Deref)))]
pub struct Foo {
#[config(default = 1, partial_attr(debug("test {bar:?}")), partial_attr(deref))]
bar: u32,
}

#[allow(dead_code)]
#[derive(Config)]
#[config(partial_attr(derive(derive_more::Debug)))]
pub struct Bar {
#[config(nested, partial_attr(debug("test2 {foo2:?}")))]
foo2: Foo,
}
}

type PartialFoo = <foo::Foo as Config>::Partial;
type PartialBar = <foo::Bar as Config>::Partial;

let partial_foo = PartialFoo { bar: Some(1) };
assert_eq!(*partial_foo, Some(1));

let partial_bar = PartialBar { foo2: partial_foo };
assert_eq!(
format!("{partial_bar:?}"),
"PartialBar { foo2: test2 PartialFoo { bar: test Some(1) } }",
);
}