Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor AsRef and AsMut derives #286

Merged
merged 33 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cf7381c
Add AsRef custom attribute parsing
MegaBluejay Aug 9, 2023
0646caa
Add field argument extraction
MegaBluejay Aug 9, 2023
3ba45e3
Use the new parser in generation
MegaBluejay Aug 9, 2023
622177b
Fix attr parsing
MegaBluejay Aug 9, 2023
c36812e
Fix naming and errors
MegaBluejay Aug 9, 2023
6acc885
Fix unknown argument error message
MegaBluejay Aug 10, 2023
1649f67
Fix unknown argument error message
MegaBluejay Aug 10, 2023
a7d53d7
Improve struct attr error span
MegaBluejay Aug 10, 2023
9313d86
Improve field attr error span
MegaBluejay Aug 10, 2023
a783ed3
Shorten datatype names
MegaBluejay Aug 10, 2023
bc31aee
Improve code organization
MegaBluejay Aug 10, 2023
a0f480d
Add compile_fail tests for AsRef
MegaBluejay Aug 10, 2023
a490696
Merge branch 'master' into as-ref-custom-parsing
MegaBluejay Aug 10, 2023
d52468f
Use the new parser for AsMut as well
MegaBluejay Aug 10, 2023
48029e7
Add compile_fail tests for AsMut
MegaBluejay Aug 10, 2023
bd984b8
Remove unused utils
MegaBluejay Aug 10, 2023
9ef87d8
Use default instead of explicit Span::call_site
MegaBluejay Aug 10, 2023
080b5c7
Merge branch 'master' into as-ref-custom-parsing
MegaBluejay Aug 14, 2023
a728466
Merge branch 'master' into as-ref-custom-parsing
tyranron Aug 15, 2023
9197edc
Some corrections [skip ci]
tyranron Aug 15, 2023
128e332
Add as_mut to compile_fail test required features
MegaBluejay Aug 15, 2023
a578681
Fix formatting
MegaBluejay Aug 15, 2023
01dbcc2
Add doc comments to AsRef/AsMut attribute parsing helpers
MegaBluejay Aug 15, 2023
bf884c2
Add ExpasionCtx type alias
MegaBluejay Aug 15, 2023
42a80a9
Refactor expansion to use a single iterator of token streams
MegaBluejay Aug 15, 2023
a3fe491
Remove add_where_clauses_for_new_ident usage
MegaBluejay Aug 15, 2023
6632fe8
Add more AsRef/AsMut forward tests
MegaBluejay Aug 16, 2023
5bfab80
Add note in docs about forwarding with other conversions
MegaBluejay Aug 16, 2023
25d4d6a
Corrections
tyranron Aug 17, 2023
a0d47fc
Reorganize AsMut tests
MegaBluejay Aug 18, 2023
65e4595
Copy tests for AsRef
MegaBluejay Aug 18, 2023
92495ba
Fix compile_fail test
MegaBluejay Aug 18, 2023
6aa56e1
Corrections [run ci]
tyranron Aug 18, 2023
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
307 changes: 247 additions & 60 deletions impl/src/as_ref.rs
Original file line number Diff line number Diff line change
@@ -1,67 +1,64 @@
use crate::utils::{
add_where_clauses_for_new_ident, AttrParams, MultiFieldData, State,
};
use proc_macro2::TokenStream;
use crate::utils::{add_where_clauses_for_new_ident, Either};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use syn::{parse::Result, DeriveInput};
use syn::{
parse::{Parse, ParseStream, Result},
spanned::Spanned,
DeriveInput, Field, Fields, Index,
};

pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let as_ref_type = format_ident!("__AsRefT");
let state = State::with_type_bound(
input,
trait_name,
"as_ref".into(),
AttrParams::ignore_and_forward(),
false,
)?;
let MultiFieldData {
fields,
input_type,
members,
infos,
trait_path,
impl_generics,
ty_generics,
where_clause,
..
} = state.enabled_fields_data();
let sub_items: Vec<_> = infos
.iter()
.zip(members.iter())
.zip(fields)
.map(|((info, member), field)| {
let field_type = &field.ty;
if info.forward {
let trait_path = quote! { #trait_path<#as_ref_type> };
let type_where_clauses = quote! {
where #field_type: #trait_path
};
let new_generics = add_where_clauses_for_new_ident(
&input.generics,
&[field],
&as_ref_type,
type_where_clauses,
false,
);
let (impl_generics, _, where_clause) = new_generics.split_for_impl();
let casted_trait = quote! { <#field_type as #trait_path> };
(
quote! { #casted_trait::as_ref(&#member) },
quote! { #impl_generics },
quote! { #where_clause },
quote! { #trait_path },
quote! { #as_ref_type },
)
} else {
(
quote! { &#member },
quote! { #impl_generics },
quote! { #where_clause },
quote! { #trait_path<#field_type> },
quote! { #field_type },
)
}
})
let trait_ident = format_ident!("{trait_name}");
let trait_path = quote! { ::derive_more::#trait_ident };

let field_args = extract_field_args(input)?;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let input_type = &input.ident;

let sub_items: Vec<_> = field_args
.into_iter()
.map(
|FieldArg {
forward,
field,
ident,
}| {
let member = quote! { self.#ident };
let field_type = &field.ty;
if forward {
let trait_path = quote! { #trait_path<#as_ref_type> };
let type_where_clauses = quote! {
where #field_type: #trait_path
};
let new_generics = add_where_clauses_for_new_ident(
&input.generics,
&[field],
&as_ref_type,
type_where_clauses,
false,
);
let (impl_generics, _, where_clause) =
new_generics.split_for_impl();
let casted_trait = quote! { <#field_type as #trait_path> };
(
quote! { #casted_trait::as_ref(&#member) },
quote! { #impl_generics },
quote! { #where_clause },
quote! { #trait_path },
quote! { #as_ref_type },
)
} else {
(
quote! { &#member },
quote! { #impl_generics },
quote! { #where_clause },
quote! { #trait_path<#field_type> },
quote! { #field_type },
)
}
},
)
.collect();
let bodies = sub_items.iter().map(|i| &i.0);
let impl_generics = sub_items.iter().map(|i| &i.1);
Expand All @@ -79,3 +76,193 @@ pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStre
}
)*})
}

enum StructAttribute {
Forward,
}

impl StructAttribute {
fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> syn::Result<Option<Self>> {
attrs
.as_ref()
.iter()
.filter(|attr| attr.path().is_ident("as_ref"))
.try_fold(None, |mut attrs, attr| {
let field_attr = attr.parse_args()?;
if attrs.replace(field_attr).is_some() {
Err(syn::Error::new(
attr.path().span(),
"only single `#[as_ref(...)]` attribute is allowed here",
MegaBluejay marked this conversation as resolved.
Show resolved Hide resolved
))
} else {
Ok(attrs)
}
})
}
}

impl Parse for StructAttribute {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<syn::Path>().and_then(|path| {
if path.is_ident("forward") {
Ok(Self::Forward)
} else {
Err(syn::Error::new(path.span(), "unknown"))
MegaBluejay marked this conversation as resolved.
Show resolved Hide resolved
}
})
}
}

enum FieldAttribute {
AsRef,
Forward,
Ignore,
}

impl FieldAttribute {
fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> syn::Result<Option<Self>> {
attrs
.as_ref()
.iter()
.filter(|attr| attr.path().is_ident("as_ref"))
.try_fold(None, |mut attrs, attr| {
let field_attr = Self::parse_attr(attr)?;
if attrs.replace(field_attr).is_some() {
Err(syn::Error::new(
attr.path().span(),
"only single `#[as_ref(...)]` attribute is allowed here",
))
} else {
Ok(attrs)
}
})
}

fn parse_attr(attr: &syn::Attribute) -> syn::Result<Self> {
if matches!(attr.meta, syn::Meta::Path(_)) {
return Ok(Self::AsRef);
}
attr.parse_args::<syn::Path>().and_then(|p| {
if p.is_ident("forward") {
return Ok(Self::Forward);
}
if p.is_ident("ignore") {
return Ok(Self::Ignore);
}
Err(syn::Error::new(p.span(), "unknown argument"))
MegaBluejay marked this conversation as resolved.
Show resolved Hide resolved
})
}
}

struct FieldArg<'a> {
forward: bool,
field: &'a Field,
ident: Either<&'a Ident, Index>,
}

impl<'a> FieldArg<'a> {
fn new(field: &'a Field, forward: bool, index: usize) -> Self {
Self {
field,
forward,
ident: field
.ident
.as_ref()
.map_or_else(|| Either::Right(syn::Index::from(index)), Either::Left),
}
}
}

fn extract_field_args(input: &'_ syn::DeriveInput) -> syn::Result<Vec<FieldArg<'_>>> {
let data = match &input.data {
syn::Data::Struct(data) => Ok(data),
syn::Data::Enum(e) => Err(syn::Error::new(
e.enum_token.span(),
"`AsRef` cannot be derived for enums",
)),
syn::Data::Union(u) => Err(syn::Error::new(
u.union_token.span(),
"`AsRef` cannot be derived for unions",
)),
}?;

if let Some(struct_attr) = StructAttribute::parse_attrs(&input.attrs)? {
let mut fields = data.fields.iter();

let field = fields.next().ok_or_else(|| {
syn::Error::new(
Span::call_site(),
"`#[as_ref(...)]` can only be applied to structs with exactly one field",
)
})?;

if FieldAttribute::parse_attrs(&field.attrs)?.is_some() {
return Err(syn::Error::new(
field.span(),
"`#[as_ref(...)]` cannot be applied to both struct and field",
));
}

if let Some(other_field) = fields.next() {
return Err(syn::Error::new(
other_field.span(),
"`#[as_ref(...)]` can only be applied to structs with exactly one field",
));
}

let forward = matches!(struct_attr, StructAttribute::Forward);

Ok(vec![FieldArg::new(field, forward, 0)])
} else {
extract_many(&data.fields)
}
}

fn extract_many(fields: &'_ Fields) -> syn::Result<Vec<FieldArg<'_>>> {
let attrs = fields
.iter()
.map(|field| FieldAttribute::parse_attrs(&field.attrs))
.collect::<syn::Result<Vec<_>>>()?;

let present_attrs = attrs
.iter()
.filter_map(|attr| attr.as_ref())
.collect::<Vec<_>>();

let all = present_attrs
.iter()
.all(|attr| matches!(attr, FieldAttribute::Ignore));

if !all
&& present_attrs
.iter()
.any(|attr| matches!(attr, FieldAttribute::Ignore))
{
return Err(syn::Error::new(
Span::call_site(),
"`#[as_ref(ignore)]` cannot be used in the same struct as other `#[as_ref(...)]` attributes",
));
}

if all {
Ok(fields
.iter()
.enumerate()
.zip(attrs)
.filter(|(_, attr)| attr.is_none())
.map(|((i, field), _)| FieldArg::new(field, false, i))
.collect())
} else {
Ok(fields
.iter()
.enumerate()
.zip(attrs)
.filter_map(|((i, field), attr)| match attr {
Some(FieldAttribute::AsRef) => Some(FieldArg::new(field, false, i)),
Some(FieldAttribute::Forward) => Some(FieldArg::new(field, true, i)),
Some(FieldAttribute::Ignore) => unreachable!(),
None => None,
})
.collect())
}
}
7 changes: 5 additions & 2 deletions impl/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ use syn::{
TypeParamBound, Variant, WhereClause,
};

#[cfg(any(feature = "from", feature = "into", feature = "as_ref"))]
pub(crate) use self::either::Either;

#[cfg(any(feature = "from", feature = "into"))]
pub(crate) use self::{either::Either, fields_ext::FieldsExt};
pub(crate) use self::fields_ext::FieldsExt;

#[derive(Clone, Copy, Default)]
pub struct DeterministicState;
Expand Down Expand Up @@ -1347,7 +1350,7 @@ pub fn is_type_parameter_used_in_type(
}
}

#[cfg(any(feature = "from", feature = "into"))]
#[cfg(any(feature = "from", feature = "into", feature = "as_ref"))]
mod either {
use proc_macro2::TokenStream;
use quote::ToTokens;
Expand Down