Skip to content

Commit

Permalink
Implement blanket implementation for Cow wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
althonos committed Mar 3, 2024
1 parent 5ef9e9f commit 4580259
Show file tree
Hide file tree
Showing 12 changed files with 322 additions and 15 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@ harness = false
name = "derive_ref"
path = "tests/derive_ref/mod.rs"
harness = false
[[test]]
name = "derive_cow"
path = "tests/derive_cow/mod.rs"
harness = false
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ provided the trait methods fit the constraints for that derive, such as
only declaring methods with `&self` of `&mut self` as their receiver.
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>` | ✔️ | | |
| Arc | `impl<T: Trait + ?Sized> Trait for Arc<T>` | ✔️ | | |
| Mut | `impl<T: Trait + ?Sized> Trait for &mut T` | ✔️ | ✔️ | |
| Box | `impl<T: Trait> Trait for Box<T>` | ✔️ | ✔️ | ✔️ |
| 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>` | ✔️ | | |
| Arc | `impl<T: Trait + ?Sized> Trait for Arc<T>` | ✔️ | | |
| Mut | `impl<T: Trait + ?Sized> Trait for &mut T` | ✔️ | ✔️ | |
| Box¹ | `impl<T: Trait + ?Sized> Trait for Box<T>` | ✔️ | ✔️ | |
| Box² | `impl<T: Trait> Trait for Box<T>` | ✔️ | ✔️ | ✔️ |
| Cow | `impl<T: Trait + ToOwned + ?Sized> Trait for Cow<_, T>` | ✔️ | | |

For instance, with our own version of `std::fmt::Write`, we can provide
an implementation for `Box<impl Write>` and `&mut impl Write`:
Expand Down
32 changes: 24 additions & 8 deletions src/derive.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use syn::parse_quote;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;

use crate::utils::deref_expr;
Expand All @@ -24,6 +25,9 @@ pub trait WrapperType {
/// The receivers allowed for this wrapper type.
const RECEIVERS: &'static [Receiver];

/// Additional types to add to the generic type bound.
const BOUNDS: &'static [&'static str] = &[];

/// Wrap the given identifier into the wrapper type.
fn wrap(ty: &syn::Ident) -> syn::Type;

Expand Down Expand Up @@ -113,16 +117,28 @@ pub trait WrapperType {
}

// Add generic type for the type we are creating ourselves
if sized {
impl_generics.params.push(syn::GenericParam::Type(
parse_quote!(#generic_type: #trait_ident #trait_generic_names),
));
} else {
impl_generics.params.push(syn::GenericParam::Type(
parse_quote!(#generic_type: #trait_ident #trait_generic_names + ?Sized),
));
let span = generic_type.span();
let mut bounds: Punctuated<_, _> = parse_quote!(#trait_ident #trait_generic_names);
if !sized {
bounds.push(parse_quote!(?Sized));
}
for bound in Self::BOUNDS {
let bound_ident = syn::Ident::new(bound, span);
bounds.push(parse_quote!(#bound_ident))
}

// Add the type wrapper in the wrapper type for the generic.
impl_generics
.params
.push(syn::GenericParam::Type(syn::TypeParam {
attrs: Vec::new(),
ident: generic_type.clone(),
colon_token: Some(syn::Token![:](span)),
bounds,
eq_token: None,
default: None,
}));

Ok(parse_quote!(
#[automatically_derived]
impl #impl_generics #trait_ident #trait_generic_names for #wrapper_type #where_clause {
Expand Down
109 changes: 109 additions & 0 deletions src/types/cow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use syn::parse_quote;

use crate::derive::Receiver;
use crate::derive::WrapperType;

struct CowType;

impl WrapperType for CowType {
const NAME: &'static str = "Cow";
const RECEIVERS: &'static [Receiver] = &[Receiver::Ref];
const BOUNDS: &'static [&'static str] = &["ToOwned"];
fn wrap(ty: &syn::Ident) -> syn::Type {
parse_quote!(std::borrow::Cow<'_, #ty>)
}
}

pub fn derive(trait_: &syn::ItemTrait) -> syn::Result<syn::ItemImpl> {
CowType::derive(trait_)
}

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

use syn::parse_quote;

#[test]
fn empty() {
let trait_ = parse_quote!(
trait MyTrait {}
);
let derived = super::super::derive(&trait_).unwrap();
assert_eq!(
derived,
parse_quote!(
#[automatically_derived]
impl<MT: MyTrait + ?Sized + ToOwned> MyTrait for std::borrow::Cow<'_, MT> {}
)
);
}

#[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 + ToOwned> Trait for std::borrow::Cow<'_, 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 MyTrait {
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 + ToOwned> MyTrait<T> for std::borrow::Cow<'_, MT> {}
)
);
}
}
}
4 changes: 4 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod arc;
mod r#box;
mod cow;
mod r#mut;
mod rc;
mod r#ref;
Expand All @@ -9,6 +10,7 @@ mod r#ref;
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum Type {
Box,
Cow,
Ref,
Mut,
Rc,
Expand All @@ -19,6 +21,7 @@ impl Type {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"Box" => Some(Type::Box),
"Cow" => Some(Type::Cow),
"Ref" => Some(Type::Ref),
"Mut" => Some(Type::Mut),
"Rc" => Some(Type::Rc),
Expand All @@ -36,6 +39,7 @@ impl Type {
pub fn defer_trait_methods(&self, trait_: &syn::ItemTrait) -> syn::Result<syn::ItemImpl> {
match self {
Type::Box => self::r#box::derive(trait_),
Type::Cow => self::cow::derive(trait_),
Type::Ref => self::r#ref::derive(trait_),
Type::Mut => self::r#mut::derive(trait_),
Type::Rc => self::rc::derive(trait_),
Expand Down
7 changes: 7 additions & 0 deletions tests/derive_cow/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
extern crate trybuild;

fn main() {
let t = trybuild::TestCases::new();
t.compile_fail(file!().replace("mod.rs", "fails/*.rs"));
t.pass(file!().replace("mod.rs", "successes/*.rs"));
}
21 changes: 21 additions & 0 deletions tests/derive_cow/successes/assoc_function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use blanket::blanket;
use impls::impls;

use std::borrow::Cow;

#[blanket(derive(Cow))]
pub trait StaticChecker {
fn check();
}

#[derive(Default, Clone)]
struct NoOpChecker;

impl StaticChecker for NoOpChecker {
fn check() {}
}

fn main() {
assert!(impls!( NoOpChecker: StaticChecker));
assert!(impls!(Cow<NoOpChecker>: StaticChecker));
}
21 changes: 21 additions & 0 deletions tests/derive_cow/successes/assoc_function_rettype.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use blanket::blanket;
use impls::impls;

use std::borrow::Cow;

#[blanket(derive(Cow))]
pub trait StaticChecker {
fn check() -> Result<(), String>;
}

#[derive(Default, Clone)]
struct NoOpChecker;

impl StaticChecker for NoOpChecker {
fn check() -> Result<(), String> { Ok(()) }
}

fn main() {
assert!(impls!( NoOpChecker: StaticChecker));
assert!(impls!(Cow<NoOpChecker>: StaticChecker));
}
33 changes: 33 additions & 0 deletions tests/derive_cow/successes/assoc_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use std::borrow::Cow;
use std::sync::Arc;
use std::sync::RwLock;

use blanket::blanket;
use impls::impls;

#[blanket(derive(Cow))]
pub trait Counter {
type Return: Clone; // <- verify this
fn increment(&self) -> Self::Return;
}

#[derive(Default, Clone)]
struct AtomicCounter {
count: Arc<RwLock<u8>>,
}

impl Counter for AtomicCounter {
// Generate something like `type Return = <A as Assoc>::Return;`.
type Return = u8;
fn increment(&self) -> u8 {
let mut guard = self.count.try_write().unwrap();
let out = *guard;
*guard += 1;
out
}
}

fn main() {
assert!(impls!(AtomicCounter: Counter));
assert!(impls!(Cow<AtomicCounter>: Counter));
}
24 changes: 24 additions & 0 deletions tests/derive_cow/successes/receiver_ref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
extern crate blanket;
extern crate impls;

use std::borrow::Cow;

use blanket::blanket;
use impls::impls;

#[blanket(derive(Cow))]
pub trait Counter {
fn count(&self);
}

#[derive(Default, Clone)]
struct AtomicCounter {}

impl Counter for AtomicCounter {
fn count(&self) {}
}

fn main() {
assert!(impls!(AtomicCounter: Counter));
assert!(impls!(Cow<AtomicCounter>: Counter));
}
30 changes: 30 additions & 0 deletions tests/derive_cow/successes/trait_generics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
extern crate blanket;
extern crate impls;

use std::borrow::Cow;

use blanket::blanket;
use impls::impls;

#[blanket(derive(Cow))]
pub trait AsRef2<T> {
fn as_ref2(&self) -> &T;
}

#[derive(Default, Clone)]
struct Owner<T> {
owned: T,
}

impl<T> AsRef2<T> for Owner<T> {
fn as_ref2(&self) -> &T {
&self.owned
}
}

fn main() {
assert!(impls!(Owner<String>: AsRef2<String>));
assert!(impls!(Cow<Owner<String>>: AsRef2<String>));
assert!(impls!(Owner<bool>: AsRef2<bool>));
assert!(impls!(Cow<Owner<bool>>: AsRef2<bool>));
}
Loading

0 comments on commit 4580259

Please sign in to comment.