Skip to content

Commit

Permalink
Basics [skip ci]
Browse files Browse the repository at this point in the history
  • Loading branch information
tyranron committed Apr 5, 2024
1 parent 5bb1b07 commit 0bf065b
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 37 deletions.
44 changes: 26 additions & 18 deletions impl/src/fmt/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ use std::fmt;

use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_quote, spanned::Spanned as _};
use syn::{parse_quote, spanned::Spanned as _, token};

use crate::utils::{attr::ParseMultiple as _, Spanning};

use super::{trait_name_to_attribute_name, ContainerAttributes};
use super::{trait_name_to_attribute_name, ContainerAttributes, FmtArgument};

/// Expands a [`fmt::Display`]-like derive macro.
///
Expand All @@ -32,7 +32,7 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenSt
let trait_ident = format_ident!("{trait_name}");
let ident = &input.ident;

let ctx = (attrs, ident, &trait_ident, &attr_name);
let ctx = (&attrs, ident, &trait_ident, &attr_name);
let (bounds, body) = match &input.data {
syn::Data::Struct(s) => expand_struct(s, ctx),
syn::Data::Enum(e) => expand_enum(e, ctx),
Expand Down Expand Up @@ -68,7 +68,7 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenSt
///
/// [`syn::Ident`]: struct@syn::Ident
type ExpansionCtx<'a> = (
ContainerAttributes,
&'a ContainerAttributes,
&'a syn::Ident,
&'a syn::Ident,
&'a syn::Ident,
Expand All @@ -79,33 +79,30 @@ fn expand_struct(
s: &syn::DataStruct,
(attrs, ident, trait_ident, _): ExpansionCtx<'_>,
) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
let mut s = Expansion {
let s = Expansion {
attrs,
fields: &s.fields,
trait_ident,
ident,
};

// It's important to generate bounds first, before we're going to modify the `fmt` expression.
let expr = s.generate_expr()?;
let bounds = s.generate_bounds();

let args = s.fields.iter().enumerate().map(|(i, f)| {
let vars = s.fields.iter().enumerate().map(|(i, f)| {
let var = f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"));
let member = f
.ident
.clone()
.map_or_else(|| syn::Member::Unnamed(i.into()), syn::Member::Named);
parse_quote! {
#var = self.#member
quote! {
let #var = &self.#member;
}
});
if let Some(fmt_attr) = &mut s.attrs.fmt {
fmt_attr.append_args(args);
}
let fmt_expr = s.generate_expr()?;

let body = quote! {
#fmt_expr
#( #vars )*
#expr
};

Ok((bounds, body))
Expand Down Expand Up @@ -142,11 +139,12 @@ fn expand_enum(
}

let v = Expansion {
attrs,
attrs: &attrs,
fields: &variant.fields,
trait_ident,
ident,
};

let arm_body = v.generate_expr()?;
bounds.extend(v.generate_bounds());

Expand Down Expand Up @@ -203,7 +201,7 @@ fn expand_union(
#[derive(Debug)]
struct Expansion<'a> {
/// Derive macro [`ContainerAttributes`].
attrs: ContainerAttributes,
attrs: &'a ContainerAttributes,

/// Struct or enum [`syn::Ident`].
///
Expand Down Expand Up @@ -234,7 +232,18 @@ impl<'a> Expansion<'a> {
Ok(if let Some((expr, trait_ident)) = fmt.transparent_call() {
quote! { derive_more::core::fmt::#trait_ident::fmt(&(#expr), __derive_more_f) }
} else {
quote! { derive_more::core::write!(__derive_more_f, #fmt) }
let mut fmt_expr = fmt.clone();
let additional_args = fmt.iter_used_fields(&self.fields).map(
|(name, _)| -> FmtArgument {
parse_quote! { #name = *#name }
},
);
fmt_expr.args.extend(additional_args);
if !fmt_expr.args.is_empty() { // TODO: Move into separate method.
fmt_expr.comma = Some(token::Comma::default());
}

quote! { derive_more::core::write!(__derive_more_f, #fmt_expr) }
})
}
None if self.fields.is_empty() => {
Expand All @@ -250,7 +259,6 @@ impl<'a> Expansion<'a> {
.iter()
.next()
.unwrap_or_else(|| unreachable!("fields.len() == 1"));
// TODO: Re-check `fmt::Pointer` scenario?
let ident = field.ident.clone().unwrap_or_else(|| format_ident!("_0"));
let trait_ident = self.trait_ident;

Expand Down
62 changes: 43 additions & 19 deletions impl/src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl BoundsAttribute {
/// ```
///
/// [`fmt`]: std::fmt
#[derive(Debug)]
#[derive(Clone, Debug)]
struct FmtAttribute {
/// Interpolation [`syn::LitStr`].
///
Expand Down Expand Up @@ -135,21 +135,6 @@ impl ToTokens for FmtAttribute {
}

impl FmtAttribute {
// TODO:
fn append_args(&mut self, more: impl IntoIterator<Item = FmtArgument>) {
let more = more
.into_iter()
.filter(|new| {
new.alias.is_none()
|| self
.args
.iter()
.all(|old| old.alias.is_none() || old.alias != new.alias)
})
.collect::<Vec<_>>();
self.args.extend(more);
}

/// Checks whether this [`FmtAttribute`] can be replaced with a transparent delegation (calling
/// a formatting trait directly instead of interpolation syntax).
///
Expand Down Expand Up @@ -211,8 +196,8 @@ impl FmtAttribute {
Some((expr, format_ident!("{trait_name}")))
}

/// Returns an [`Iterator`] over bounded [`syn::Type`]s (and correspondent trait names) by this
/// [`FmtAttribute`].
/// Returns an [`Iterator`] over bounded [`syn::Type`]s (and correspondent trait names) of the
/// provided [`syn::Fields`] used by this [`FmtAttribute`].
fn bounded_types<'a>(
&'a self,
fields: &'a syn::Fields,
Expand Down Expand Up @@ -250,6 +235,45 @@ impl FmtAttribute {
})
}

/// Returns an [`Iterator`] over the provided [`syn::Field`]s used by this [`FmtAttribute`],
/// along with the correspondent [`syn::Ident`] it's referred by in this [`FmtAttribute`].
fn iter_used_fields<'a>(
&'a self,
fields: &'a syn::Fields,
) -> impl Iterator<Item = (syn::Ident, &'a syn::Field)> {
let placeholders = Placeholder::parse_fmt_string(&self.lit.value());

// We ignore unknown fields, as compiler will produce better error messages.
placeholders.into_iter().filter_map(move |placeholder| {
let name = match &placeholder.arg {
Parameter::Named(name) => self
.args
.iter()
.find_map(|a| (a.alias()? == &name).then_some(&a.expr))
.map_or(Some(format_ident!("{name}")), |expr| expr.ident().cloned())?,
Parameter::Positional(i) => self
.args
.iter()
.nth(*i)
.and_then(|a| a.expr.ident().filter(|_| a.alias.is_none()))?
.clone(),
};
let position = name.to_string().strip_prefix('_').and_then(|s| s.parse().ok());

let field = match (&fields, position) {
(syn::Fields::Unnamed(f), Some(i)) => {
f.unnamed.iter().nth(i)
}
(syn::Fields::Named(f), None) => f.named.iter().find_map(|f| {
f.ident.as_ref().filter(|s| **s == name).map(|_| f)
}),
_ => None,
}?;

Some((name, field))
})
}

/// Errors in case legacy syntax is encountered: `fmt = "...", (arg),*`.
fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> {
let fork = input.fork();
Expand Down Expand Up @@ -296,7 +320,7 @@ impl FmtAttribute {
/// Representation of a [named parameter][1] (`identifier '=' expression`) in a [`FmtAttribute`].
///
/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters
#[derive(Debug)]
#[derive(Clone, Debug)]
struct FmtArgument {
/// `identifier =` [`Ident`].
///
Expand Down

0 comments on commit 0bf065b

Please sign in to comment.