Skip to content

Commit

Permalink
Add implementation for #[blanket(derive(Rc))] with associated tests
Browse files Browse the repository at this point in the history
  • Loading branch information
althonos committed Jul 21, 2020
1 parent ec3db37 commit 7560e67
Show file tree
Hide file tree
Showing 13 changed files with 373 additions and 9 deletions.
12 changes: 7 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,18 @@ features = ["full"]
trybuild = "1.0"

[features]
default = []
_doc = []

[[test]]
name = "derive_ref"
path = "tests/derive_ref/mod.rs"

[[test]]
name = "derive_box"
path = "tests/derive_box/mod.rs"

[[test]]
name = "derive_mut"
path = "tests/derive_mut/mod.rs"
[[test]]
name = "derive_rc"
path = "tests/derive_rc/mod.rs"
[[test]]
name = "derive_ref"
path = "tests/derive_ref/mod.rs"
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ The following derives are available:
| Derive | Impl block | `fn (&self)` | `fn (&mut self)` | `fn (self)` |
|--------|--------------------------------------------|--------------|------------------|-------------|
| Ref | `impl<T: Trait + ?Sized> Trait for &T` | ✔️ | | |
| Rc | `impl<T: Trait + ?Sized> Trait for Rc<T>` | ✔️ | | |
| Mut | `impl<T: Trait + ?Sized> Trait for &mut T` | ✔️ | ✔️ | |
| Box | `impl<T: Trait> Trait for Box<T>` | ✔️ | ✔️ | ✔️ |

Expand Down Expand Up @@ -90,7 +91,7 @@ to provide a default behaviour as an external function, such as what
The following example implements a very simple visitor trait for types
able to process a `&str` char-by-char.

```rust,ignore
```rust
extern crate blanket;
use blanket::blanket;

Expand All @@ -117,7 +118,7 @@ mod visitor {
and then create a default implementation for all of the declared methods,
generating the following code:

```rust,ignore
```rust
trait Visitor {
fn visit_string(&self, s: &str) {
visitor::visit_string(self, s)
Expand All @@ -131,12 +132,12 @@ trait Visitor {
## ✒️ To-Do

- ✔️ Delegation of default method to external functions.
- ✔️ Support for traits with generic arguments.
- ✔️ `#[derive(Ref)]`
- ✔️ `#[derive(Mut)]`
- ✔️ `#[derive(Box)]`
- ✔️ `#[derive(Rc)]`
- ❌ Update `Box` to allow unsized types if possible.
- ❌ Support for traits with generic arguments.
-`#[derive(Rc)]`
-`#[derive(Arc)]`

## 📋 Changelog
Expand Down
4 changes: 4 additions & 0 deletions src/derive/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
mod r#box;
mod r#mut;
mod rc;
mod r#ref;

#[derive(Debug, PartialEq, Eq, Hash)]
pub enum Derive {
Box,
Ref,
Mut,
Rc,
}

impl Derive {
Expand All @@ -15,6 +17,7 @@ impl Derive {
"Box" => Some(Derive::Box),
"Ref" => Some(Derive::Ref),
"Mut" => Some(Derive::Mut),
"Rc" => Some(Derive::Rc),
_ => None,
}
}
Expand All @@ -30,6 +33,7 @@ impl Derive {
Derive::Box => self::r#box::derive(trait_),
Derive::Ref => self::r#ref::derive(trait_),
Derive::Mut => self::r#mut::derive(trait_),
Derive::Rc => self::rc::derive(trait_),
}
}
}
149 changes: 149 additions & 0 deletions src/derive/rc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use syn::parse_quote;
use syn::spanned::Spanned;

use crate::utils::deref_expr;
use crate::utils::signature_to_method_call;
use crate::utils::trait_to_generic_ident;

pub fn derive(trait_: &syn::ItemTrait) -> syn::Result<syn::ItemImpl> {
// build the methods
let mut methods: Vec<syn::ImplItemMethod> = Vec::new();
for item in trait_.items.iter() {
if let syn::TraitItem::Method(ref m) = item {
if let Some(receiver) = m.sig.receiver() {
match receiver {
syn::FnArg::Receiver(r) if r.mutability.is_some() => {
let msg = "cannot derive `Rc` for a trait declaring `&mut self` methods";
return Err(syn::Error::new(r.span(), msg));
}
syn::FnArg::Receiver(r) if r.reference.is_none() => {
let msg = "cannot derive `Rc` for a trait declaring `self` methods";
return Err(syn::Error::new(r.span(), msg));
}
syn::FnArg::Typed(pat) => {
let msg = "cannot derive `Rc` for a trait declaring methods with arbitrary receiver types";
return Err(syn::Error::new(pat.span(), msg));
}
_ => (),
}
}

let mut call = signature_to_method_call(&m.sig)?;
call.receiver = Box::new(deref_expr(deref_expr(*call.receiver)));

let signature = &m.sig;
let item = parse_quote!(#[inline] #signature { #call });
methods.push(item)
}
}

// build an identifier for the generic type used for the implementation
let trait_ident = &trait_.ident;
let generic_type = trait_to_generic_ident(&trait_);

// build the generics for the impl block:
// we use the same generics as the trait itself, plus
// a generic type that implements the trait for which we provide the
// blanket implementation
let trait_generics = &trait_.generics;
let mut impl_generics = trait_generics.clone();
impl_generics.params.push(syn::GenericParam::Type(
parse_quote!(#generic_type: #trait_ident #trait_generics + ?Sized),
));

Ok(parse_quote!(
#[automatically_derived]
impl #impl_generics #trait_ident #trait_generics for std::rc::Rc<#generic_type> {
#(#methods)*
}
))
}

#[cfg(test)]
mod tests {
mod derive {

use syn::parse_quote;

#[test]
fn empty() {
let trait_ = parse_quote!(
trait Trait {}
);
assert_eq!(
super::super::derive(&trait_).unwrap(),
parse_quote!(
#[automatically_derived]
impl<T: Trait + ?Sized> Trait for std::rc::Rc<T> {}
)
);
}

#[test]
fn receiver_ref() {
let trait_ = parse_quote!(
trait Trait {
fn my_method(&self);
}
);
assert_eq!(
super::super::derive(&trait_).unwrap(),
parse_quote!(
#[automatically_derived]
impl<T: Trait + ?Sized> Trait for std::rc::Rc<T> {
#[inline]
fn my_method(&self) {
(*(*self)).my_method()
}
}
)
);
}

#[test]
fn receiver_mut() {
let trait_ = parse_quote!(
trait Trait {
fn my_method(&mut self);
}
);
assert!(super::super::derive(&trait_).is_err());
}

#[test]
fn receiver_self() {
let trait_ = parse_quote!(
trait Trait {
fn my_method(self);
}
);
assert!(super::super::derive(&trait_).is_err());
}

#[test]
fn receiver_arbitrary() {
let trait_ = parse_quote!(
trait Trait {
fn my_method(self: Box<Self>);
}
);
assert!(super::super::derive(&trait_).is_err());
}

#[test]
fn generics() {
let trait_ = parse_quote!(
trait MyTrait<T> {}
);
let derived = super::super::derive(&trait_).unwrap();

assert_eq!(
derived,
parse_quote!(
#[automatically_derived]
impl<T, MT: MyTrait<T> + ?Sized> MyTrait<T> for std::rc::Rc<MT> {}
)
);
}
}
}
10 changes: 10 additions & 0 deletions tests/derive_rc/fails/boxmethods.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
extern crate blanket;

use blanket::blanket;

#[blanket(derive(Rc))]
pub trait Counter {
fn increment(self: Box<Self>);
}

fn main() {}
5 changes: 5 additions & 0 deletions tests/derive_rc/fails/boxmethods.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: cannot derive `Rc` for a trait declaring methods with arbitrary receiver types
--> $DIR/boxmethods.rs:7:18
|
7 | fn increment(self: Box<Self>);
| ^^^^^^^^^^^^^^^
10 changes: 10 additions & 0 deletions tests/derive_rc/fails/mutmethods.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
extern crate blanket;

use blanket::blanket;

#[blanket(derive(Rc))]
pub trait Counter {
fn increment(&mut self);
}

fn main() {}
5 changes: 5 additions & 0 deletions tests/derive_rc/fails/mutmethods.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: cannot derive `Rc` for a trait declaring `&mut self` methods
--> $DIR/mutmethods.rs:7:18
|
7 | fn increment(&mut self);
| ^^^^^^^^^
37 changes: 37 additions & 0 deletions tests/derive_rc/fails/noderive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::rc::Rc;
use std::sync::atomic::AtomicU8;
use std::sync::atomic::Ordering;

pub trait Counter {
fn increment(&self);
}

#[derive(Default)]
struct AtomicCounter {
count: AtomicU8
}

impl Counter for AtomicCounter {
fn increment(&self) {
self.count.fetch_add(1, Ordering::SeqCst);
}
}

struct CounterWrapper<C: Counter> {
inner: C
}

impl<C: Counter> From<C> for CounterWrapper<C> {
fn from(inner: C) -> Self {
Self { inner }
}
}

fn main() {
// counter wrapper should be able to wrap AtomicCounter
let counter = AtomicCounter::default();
let wrapper_by_value = CounterWrapper::from(counter);
// but this will fail because no implementation was derived
let counter = AtomicCounter::default();
let wrapper_by_ref = CounterWrapper::from(Rc::new(counter));
}
17 changes: 17 additions & 0 deletions tests/derive_rc/fails/noderive.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error[E0277]: the trait bound `std::rc::Rc<AtomicCounter>: Counter` is not satisfied
--> $DIR/noderive.rs:36:47
|
20 | struct CounterWrapper<C: Counter> {
| ------- required by this bound in `CounterWrapper`
...
36 | let wrapper_by_ref = CounterWrapper::from(Rc::new(counter));
| ^^^^^^^^^^^^^^^^ the trait `Counter` is not implemented for `std::rc::Rc<AtomicCounter>`

error[E0277]: the trait bound `std::rc::Rc<AtomicCounter>: Counter` is not satisfied
--> $DIR/noderive.rs:36:26
|
20 | struct CounterWrapper<C: Counter> {
| ------- required by this bound in `CounterWrapper`
...
36 | let wrapper_by_ref = CounterWrapper::from(Rc::new(counter));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Counter` is not implemented for `std::rc::Rc<AtomicCounter>`
10 changes: 10 additions & 0 deletions tests/derive_rc/fails/selfmethods.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
extern crate blanket;

use blanket::blanket;

#[blanket(derive(Rc))]
pub trait Extract {
fn extract(self);
}

fn main() {}
5 changes: 5 additions & 0 deletions tests/derive_rc/fails/selfmethods.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: cannot derive `Rc` for a trait declaring `self` methods
--> $DIR/selfmethods.rs:7:16
|
7 | fn extract(self);
| ^^^^
Loading

0 comments on commit 7560e67

Please sign in to comment.