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

Add Self bounds from methods and ?Sized relaxation (if possible) to impl header #54

Merged
merged 3 commits into from
Jul 21, 2019
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
111 changes: 106 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,112 @@ 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 }
};

// Check if there are some `Self: Foo` bounds on methods. If so, we
// need to add those bounds to `T` as well. See issue #11 for more
// information, but in short: there is no better solution. Method where
// clauses with `Self: Foo` force us to add `T: Foo` to the impl
// header, as we otherwise cannot generate a valid impl block.
let additional_bounds = 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))
// Exact all relevant bounds
.flat_map(|m| {
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 }
})
.filter(|p| {
// Only `Self:` bounds
match &p.bounded_ty {
Type::Path(p) => p.path.is_ident("Self"),
_ => false,
}
})
.flat_map(|p| &p.bounds)
});

// 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 #(+ #additional_bounds)* }
),
ProxyType::Box | ProxyType::Rc | ProxyType::Arc => {
(quote!{}, quote! { : #trait_path #relaxation #(+ #additional_bounds)* })
}
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>>();
}
23 changes: 23 additions & 0 deletions tests/compile-pass/self_bound.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use auto_impl::auto_impl;


#[auto_impl(&)]
trait Trait {
fn foo(&self)
where Self: Clone;
}

#[derive(Clone)]
struct Foo {}
impl Trait for Foo {
fn foo(&self)
where Self: Clone,
{}
}

fn assert_impl<T: Trait>() {}

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


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

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

fn assert_impl<T: Trait>() {}

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

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


#[auto_impl(&)]
trait Trait {
fn foo(&self)
where Self: Clone;
fn bar(&self)
where Self: Default + fmt::Display;
}

#[derive(Clone, Default)]
struct Foo {}
impl Trait for Foo {
fn foo(&self)
where Self: Clone,
{}
fn bar(&self)
where Self: Default + fmt::Display,
{}
}

impl fmt::Display for Foo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
unimplemented!()
}
}

fn assert_impl<T: Trait>() {}

fn main() {
assert_impl::<Foo>();
assert_impl::<&Foo>();
}
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>>();
}
17 changes: 17 additions & 0 deletions tests/compile-pass/trait_obj_self_sized.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use auto_impl::auto_impl;


#[auto_impl(Box)]
trait Foo: Sized {
fn foo(&self);
}

#[auto_impl(Box)]
trait Bar where Self: Sized {
fn foo(&self);
}

#[auto_impl(Box)]
trait Baz: Sized where Self: Sized {
fn foo(&self);
}
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);
}