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

Implement bound(*) to mean "all generic parameters" #22

Merged
merged 20 commits into from
Jun 1, 2024
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
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,29 @@ enum Enum<T, K> {

In the above case, `T` is bound to the `Debug` trait, but `K` is not.

Or, you can have `educe` replicate the behaviour of `std`'s `derive`'s,
where a bound is produced for *every* generic parameter,
without regard to how it's used in the structure:

```rust
#[derive(Educe)]
#[educe(Debug(bound(*)))]
struct Struct<T> {
#[educe(Debug(ignore))]
f: T,
}
```

This can be useful if you don't want to make the trait implementation
part of your permanent public API.
In this example,
`Struct<T>` doesn't implement `Debug` unless `T` does.
I.e., it has a `T: Debug` bound even though that's not needed right now.
Later we might want to display `f`; we wouldn't then need to make
a breaking API change by adding the bound.

This was the behaviour of `Trait(bound)` in educe 0.4.x and earlier.

###### Union

A union will be formatted as a `u8` slice because we don't know its fields at runtime. The fields of a union cannot be ignored, renamed, or formatted with other methods. The implementation is **unsafe** because it may expose uninitialized memory.
Expand Down
23 changes: 6 additions & 17 deletions src/common/bound.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use syn::{punctuated::Punctuated, token::Comma, GenericParam, Meta, Path, Type, WherePredicate};

use crate::common::where_predicates_bool::{
create_where_predicates_from_generic_parameters,
create_where_predicates_from_all_generic_parameters,
create_where_predicates_from_generic_parameters_check_types, meta_2_where_predicates,
WherePredicates, WherePredicatesOrBool,
};
Expand All @@ -10,6 +10,7 @@ pub(crate) enum Bound {
Disabled,
Auto,
Custom(WherePredicates),
All,
}

impl Bound {
Expand All @@ -28,41 +29,29 @@ impl Bound {
Self::Disabled
}
},
WherePredicatesOrBool::All => Self::All,
})
}
}

impl Bound {
#[inline]
pub(crate) fn into_where_predicates_by_generic_parameters(
self,
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
) -> Punctuated<WherePredicate, Comma> {
match self {
Self::Disabled => Punctuated::new(),
Self::Auto => create_where_predicates_from_generic_parameters(params, bound_trait),
Self::Custom(where_predicates) => where_predicates,
}
}

#[inline]
pub(crate) fn into_where_predicates_by_generic_parameters_check_types(
self,
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
types: &[&Type],
recursive: Option<(bool, bool, bool)>,
supertraits: &[proc_macro2::TokenStream],
) -> Punctuated<WherePredicate, Comma> {
match self {
Self::Disabled => Punctuated::new(),
Self::Auto => create_where_predicates_from_generic_parameters_check_types(
params,
bound_trait,
types,
recursive,
supertraits,
),
Self::Custom(where_predicates) => where_predicates,
Self::All => create_where_predicates_from_all_generic_parameters(params, bound_trait),
}
}
}
113 changes: 1 addition & 112 deletions src/common/type.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use std::collections::HashSet;

use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
GenericArgument, Ident, Meta, Path, PathArguments, Token, Type, TypeParamBound,
Meta, Token, Type,
};

pub(crate) struct TypeWithPunctuatedMeta {
Expand Down Expand Up @@ -34,115 +32,6 @@ impl Parse for TypeWithPunctuatedMeta {
}
}

/// recursive (dereference, de_ptr, de_param)
#[inline]
pub(crate) fn find_idents_in_path<'a>(
set: &mut HashSet<&'a Ident>,
path: &'a Path,
recursive: Option<(bool, bool, bool)>,
) {
if let Some((_, _, de_param)) = recursive {
if de_param {
if let Some(segment) = path.segments.iter().last() {
if let PathArguments::AngleBracketed(a) = &segment.arguments {
// the ident is definitely not a generic parameter, so we don't insert it

for arg in a.args.iter() {
match arg {
GenericArgument::Type(ty) => {
find_idents_in_type(set, ty, recursive);
},
GenericArgument::AssocType(ty) => {
find_idents_in_type(set, &ty.ty, recursive);
},
_ => (),
}
}

return;
}
}
}
}

if let Some(ty) = path.get_ident() {
set.insert(ty);
}
}

/// recursive (dereference, de_ptr, de_param)
#[inline]
pub(crate) fn find_idents_in_type<'a>(
set: &mut HashSet<&'a Ident>,
ty: &'a Type,
recursive: Option<(bool, bool, bool)>,
) {
match ty {
Type::Array(ty) => {
if recursive.is_some() {
find_idents_in_type(set, ty.elem.as_ref(), recursive);
}
},
Type::Group(ty) => {
if recursive.is_some() {
find_idents_in_type(set, ty.elem.as_ref(), recursive);
}
},
Type::ImplTrait(ty) => {
// always recursive
for b in &ty.bounds {
if let TypeParamBound::Trait(ty) = b {
find_idents_in_path(set, &ty.path, recursive);
}
}
},
Type::Macro(ty) => {
if recursive.is_some() {
find_idents_in_path(set, &ty.mac.path, recursive);
}
},
Type::Paren(ty) => {
if recursive.is_some() {
find_idents_in_type(set, ty.elem.as_ref(), recursive);
}
},
Type::Path(ty) => {
find_idents_in_path(set, &ty.path, recursive);
},
Type::Ptr(ty) => {
if let Some((_, true, _)) = recursive {
find_idents_in_type(set, ty.elem.as_ref(), recursive);
}
},
Type::Reference(ty) => {
if let Some((true, ..)) = recursive {
find_idents_in_type(set, ty.elem.as_ref(), recursive);
}
},
Type::Slice(ty) => {
if recursive.is_some() {
find_idents_in_type(set, ty.elem.as_ref(), recursive);
}
},
Type::TraitObject(ty) => {
// always recursive
for b in &ty.bounds {
if let TypeParamBound::Trait(ty) = b {
find_idents_in_path(set, &ty.path, recursive);
}
}
},
Type::Tuple(ty) => {
if recursive.is_some() {
for ty in &ty.elems {
find_idents_in_type(set, ty, recursive)
}
}
},
_ => (),
}
}

#[inline]
pub(crate) fn dereference(ty: &Type) -> &Type {
if let Type::Reference(ty) = ty {
Expand Down
28 changes: 11 additions & 17 deletions src/common/where_predicates_bool.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::collections::HashSet;

use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
Expand All @@ -9,13 +7,14 @@ use syn::{
Expr, GenericParam, Lit, Meta, MetaNameValue, Path, Token, Type, WherePredicate,
};

use super::{path::path_to_string, r#type::find_idents_in_type};
use super::path::path_to_string;

pub(crate) type WherePredicates = Punctuated<WherePredicate, Token![,]>;

pub(crate) enum WherePredicatesOrBool {
WherePredicates(WherePredicates),
Bool(bool),
All,
}

impl Parse for WherePredicatesOrBool {
Expand All @@ -35,6 +34,10 @@ impl Parse for WherePredicatesOrBool {
}
}

if let Ok(_star) = input.parse::<Token![*]>() {
return Ok(Self::All);
}

Ok(Self::WherePredicates(input.parse_terminated(WherePredicate::parse, Token![,])?))
}
}
Expand Down Expand Up @@ -89,7 +92,7 @@ pub(crate) fn meta_2_where_predicates(meta: &Meta) -> syn::Result<WherePredicate
}

#[inline]
pub(crate) fn create_where_predicates_from_generic_parameters(
pub(crate) fn create_where_predicates_from_all_generic_parameters(
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
) -> WherePredicates {
Expand All @@ -108,27 +111,18 @@ pub(crate) fn create_where_predicates_from_generic_parameters(

#[inline]
pub(crate) fn create_where_predicates_from_generic_parameters_check_types(
params: &Punctuated<GenericParam, Comma>,
bound_trait: &Path,
types: &[&Type],
recursive: Option<(bool, bool, bool)>,
supertraits: &[proc_macro2::TokenStream],
) -> WherePredicates {
let mut where_predicates = Punctuated::new();

let mut set = HashSet::new();

for t in types {
find_idents_in_type(&mut set, t, recursive);
where_predicates.push(syn::parse2(quote! { #t: #bound_trait }).unwrap());
}

for param in params {
if let GenericParam::Type(ty) = param {
let ident = &ty.ident;

if set.contains(ident) {
where_predicates.push(syn::parse2(quote! { #ident: #bound_trait }).unwrap());
}
}
for supertrait in supertraits {
where_predicates.push(syn::parse2(quote! { Self: #supertrait }).unwrap());
}

where_predicates
Expand Down
2 changes: 1 addition & 1 deletion src/trait_handlers/clone/clone_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ impl TraitHandler for CloneEnumHandler {
})
.unwrap(),
&clone_types,
Some((false, false, false)),
&[],
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/trait_handlers/clone/clone_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ impl TraitHandler for CloneStructHandler {
})
.unwrap(),
&clone_types,
Some((false, false, false)),
&[],
);
}

Expand Down
7 changes: 6 additions & 1 deletion src/trait_handlers/clone/clone_union.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ impl TraitHandler for CloneUnionHandler {
}
.build_from_clone_meta(meta)?;

let mut field_types = vec![];

if let Data::Union(data) = &ast.data {
for field in data.fields.named.iter() {
field_types.push(&field.ty);
let _ = FieldAttributeBuilder {
enable_method: false
}
Expand All @@ -32,9 +35,11 @@ impl TraitHandler for CloneUnionHandler {

let ident = &ast.ident;

let bound = type_attribute.bound.into_where_predicates_by_generic_parameters(
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::marker::Copy)).unwrap(),
&field_types,
&[],
);

let where_clause = ast.generics.make_where_clause();
Expand Down
28 changes: 12 additions & 16 deletions src/trait_handlers/copy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ impl TraitHandler for CopyHandler {
}
.build_from_copy_meta(meta)?;

let mut field_types = vec![];

// if `contains_clone` is true, the implementation is handled by the `Clone` attribute, and field attributes is also handled by the `Clone` attribute
if !contains_clone {
match &ast.data {
Data::Struct(data) => {
for field in data.fields.iter() {
field_types.push(&field.ty);
let _ =
FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?;
}
Expand All @@ -46,13 +49,15 @@ impl TraitHandler for CopyHandler {
.build_from_attributes(&variant.attrs, traits)?;

for field in variant.fields.iter() {
field_types.push(&field.ty);
let _ = FieldAttributeBuilder
.build_from_attributes(&field.attrs, traits)?;
}
}
},
Data::Union(data) => {
for field in data.fields.named.iter() {
field_types.push(&field.ty);
let _ =
FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?;
}
Expand All @@ -61,22 +66,13 @@ impl TraitHandler for CopyHandler {

let ident = &ast.ident;

/*
#[derive(Clone)]
struct B<T> {
f1: PhantomData<T>,
}

impl<T> Copy for B<T> {

}

// The above code will throw a compile error because T have to be bound to `Copy`. However, it seems not to be necessary logically.
*/
let bound = type_attribute.bound.into_where_predicates_by_generic_parameters(
&ast.generics.params,
&syn::parse2(quote!(::core::marker::Copy)).unwrap(),
);
let bound =
type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
&ast.generics.params,
&syn::parse2(quote!(::core::marker::Copy)).unwrap(),
&field_types,
&[quote! {::core::clone::Clone}],
);

let where_clause = ast.generics.make_where_clause();

Expand Down
Loading
Loading