Skip to content

Commit

Permalink
Add ?Sized relaxation to impl header whenever possible
Browse files Browse the repository at this point in the history
It's only impossible when there is `Self` by value in in parameter or
return position in any method. This commit does not yet check for
`Self` in non-receiver parameters as this crate cannot deal with that
yet anyway.

Furthermore, a `where Self: Sized` bound on methods does not help in
that we still cannot add the `?Sized` relaxation. This is related to
issue #11.
  • Loading branch information
LukasKalbertodt committed Jul 16, 2019
1 parent 52b3794 commit 82bf029
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 5 deletions.
83 changes: 78 additions & 5 deletions src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use crate::proc_macro::Span;
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, TokenStreamExt};
use syn::{
FnArg, Ident, ItemTrait, Lifetime, MethodSig, Pat, PatIdent, TraitItem, TraitItemConst,
TraitItemMethod, TraitItemType,
FnArg, Ident, ItemTrait, Lifetime, MethodSig, Pat, PatIdent, ReturnType, TraitBound,
TraitBoundModifier, TraitItem, TraitItemConst, TraitItemMethod, TraitItemType, Type,
TypeParamBound, WherePredicate,
};

use crate::{
Expand Down Expand Up @@ -65,12 +66,84 @@ fn gen_header(
// The `'{proxy_lt_param}` in the beginning is only added when the proxy
// type is `&` or `&mut`.
let impl_generics = {
// Determine whether we can add a `?Sized` relaxation to allow trait
// objects. We can do that as long as there is no method that has a
// `self` by value receiver and no `where Self: Sized` bound.
let sized_required = trait_def.items.iter()
// Only interested in methods
.filter_map(|item| if let TraitItem::Method(m) = item { Some(m) } else { None })
// We also ignore methods that we will not override. In the case of
// invalid attributes it is save to assume default behavior.
.filter(|m| !should_keep_default_for(m, proxy_type).unwrap_or(false))
.any(|m| {
// Check if there is a `Self: Sized` bound on the method.
let self_is_bounded_sized = m.sig.decl.generics.where_clause.iter()
.flat_map(|wc| &wc.predicates)
.filter_map(|pred| {
if let WherePredicate::Type(p) = pred { Some(p) } else { None }
})
.any(|pred| {
// Check if the type is `Self`
match &pred.bounded_ty {
Type::Path(p) if p.path.is_ident("Self") => {
// Check if the bound contains `Sized`
pred.bounds.iter().any(|b| {
match b {
TypeParamBound::Trait(TraitBound {
modifier: TraitBoundModifier::None,
path,
..
}) => path.is_ident("Sized"),
_ => false,
}
})
}
_ => false,
}
});

// Check if the first parameter is `self` by value. In that
// case, we might require `Self` to be `Sized`.
let self_value_param = match m.sig.decl.inputs.first().map(|p| p.into_value()) {
Some(FnArg::SelfValue(_)) => true,
_ => false,
};

// Check if return type is `Self`
let self_value_return = match &m.sig.decl.output {
ReturnType::Type(_, t) => {
if let Type::Path(p) = &**t {
p.path.is_ident("Self")
} else {
false
}
}
_ => false,
};

// TODO: check for `Self` parameter in any other argument.

// If for this method, `Self` is used in a position that
// requires `Self: Sized` or this bound is added explicitly, we
// cannot add the `?Sized` relaxation to the impl body.
self_value_param || self_value_return || self_is_bounded_sized
});

let relaxation = if sized_required {
quote! {}
} else {
quote! { + ?::std::marker::Sized }
};

// Determine if our proxy type needs a lifetime parameter
let (mut params, ty_bounds) = match proxy_type {
ProxyType::Ref | ProxyType::RefMut => {
(quote! { #proxy_lt_param, }, quote! { : #proxy_lt_param + #trait_path })
ProxyType::Ref | ProxyType::RefMut => (
quote! { #proxy_lt_param, },
quote! { : #proxy_lt_param + #trait_path #relaxation }
),
ProxyType::Box | ProxyType::Rc | ProxyType::Arc => {
(quote!{}, quote! { : #trait_path #relaxation })
}
ProxyType::Box | ProxyType::Rc | ProxyType::Arc => (quote!{}, quote! { : #trait_path }),
ProxyType::Fn | ProxyType::FnMut | ProxyType::FnOnce => {
let fn_bound = gen_fn_type_for_trait(proxy_type, trait_def)?;
(quote!{}, quote! { : #fn_bound })
Expand Down
13 changes: 13 additions & 0 deletions tests/compile-fail/trait_obj_value_self.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use auto_impl::auto_impl;


#[auto_impl(Box)]
trait Trait {
fn foo(self);
}

fn assert_impl<T: Trait>() {}

fn main() {
assert_impl::<Box<dyn Trait>>();
}
22 changes: 22 additions & 0 deletions tests/compile-pass/trait_obj_default_method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use auto_impl::auto_impl;


#[auto_impl(Box)]
trait Trait {
fn bar(&self);

#[auto_impl(keep_default_for(Box))]
fn foo(self) where Self: Sized {}
}

fn assert_impl<T: Trait>() {}

struct Foo {}
impl Trait for Foo {
fn bar(&self) {}
}

fn main() {
assert_impl::<Foo>();
assert_impl::<Box<dyn Trait>>();
}
19 changes: 19 additions & 0 deletions tests/compile-pass/trait_obj_immutable_self.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use auto_impl::auto_impl;


#[auto_impl(&, &mut, Box, Rc, Arc)]
trait Trait {
fn foo(&self);
}

fn assert_impl<T: Trait>() {}

fn main() {
use std::{rc::Rc, sync::Arc};

assert_impl::<&dyn Trait>();
assert_impl::<&mut dyn Trait>();
assert_impl::<Box<dyn Trait>>();
assert_impl::<Rc<dyn Trait>>();
assert_impl::<Arc<dyn Trait>>();
}
7 changes: 7 additions & 0 deletions tests/compile-pass/trait_obj_value_self.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use auto_impl::auto_impl;


#[auto_impl(Box)]
trait Trait {
fn foo(self);
}

0 comments on commit 82bf029

Please sign in to comment.