Skip to content

Support auto_impl on borrowed references #4

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

Merged
merged 1 commit into from
Oct 7, 2017
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
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "auto_impl"
version = "0.1.1"
version = "0.1.2"
Copy link

@bjorn3 bjorn3 Oct 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be 0.2.0 as this pr is a functional change.

http://semver.org/#spec-item-6

  1. Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards compatible bug fixes are introduced. A bug fix is defined as an internal change that fixes incorrect behavior.
  2. Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards compatible functionality is introduced to the public API. It MUST be incremented if any public API functionality is marked as deprecated. It MAY be incremented if substantial new functionality or improvements are introduced within the private code. It MAY include patch level changes. Patch version MUST be reset to 0 when minor version is incremented.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's pretty common in the Rust ecosystem to use the patch version effectively as the minor is post 1.0 when the crate is pre 1.0, since we're only utilising two versions and aren't technically bound by semver anyways in 0.x.

If there's code that was accepted in 0.1.1 that isn't in 0.1.2 then I've messed up.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see.

authors = ["Ashley Mannix <[email protected]>"]
license = "MIT"
description = "Automatically implement traits for common smart pointers and closures"
Expand All @@ -18,4 +18,4 @@ proc-macro = true

[dependencies]
quote = "~0.3"
syn = { version = "~0.11", features = ["full"] }
syn = { version = "~0.11", features = ["full"] }
39 changes: 36 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ The following types are supported:
- [`Fn`](https://doc.rust-lang.org/std/ops/trait.Fn.html)
- [`FnMut`](https://doc.rust-lang.org/std/ops/trait.FnMut.html)
- [`FnOnce`](https://doc.rust-lang.org/std/ops/trait.FnOnce.html)
- `&`
- `&mut`

## Implement a trait for a smart pointer

Expand Down Expand Up @@ -108,16 +110,16 @@ There are a few restrictions on `#[auto_impl]` for smart pointers. The trait mus

```rust
#[auto_impl(Fn)]
trait FnTrait2<'a, T> {
trait MyTrait<'a, T> {
fn execute<'b>(&'a self, arg1: &'b T, arg2: &'static str) -> Result<(), String>;
}
```

Will expand to:

```rust
impl<'a, T, TFn> FnTrait2<'a, T> for TFn
where TFn: Fn(&T, &'static str) -> Result<(), String>
impl<'a, T, TAutoImpl> MyTrait<'a, T> for TAutoImpl
where TAutoImpl: Fn(&T, &'static str) -> Result<(), String>
{
fn execute<'b>(&'a self, arg1: &'b T, arg1: &'static str) -> Result<(), String> {
self(arg1, arg2)
Expand All @@ -130,3 +132,34 @@ There are a few restrictions on `#[auto_impl]` for closures. The trait must:
- Have a single method
- Have no associated types
- Have no non-static lifetimes in the return type

## Implement a trait for a borrowed reference

```rust
#[auto_impl(&, &mut)]
trait MyTrait<'a, T> {
fn execute<'b>(&'a self, arg1: &'b T, arg2: &'static str) -> Result<(), String>;
}
```

Will expand to:

```rust
impl<'auto, 'a, T, TAutoImpl> MyTrait<'a, T> for &'auto TAutoImpl {
fn execute<'b>(&'a self, arg1: &'b T, arg1: &'static str) -> Result<(), String> {
(**self).execute(arg1, arg2)
}
}

impl<'auto, 'a, T, TAutoImpl> MyTrait<'a, T> for &'auto mut TAutoImpl {
fn execute<'b>(&'a self, arg1: &'b T, arg1: &'static str) -> Result<(), String> {
(**self).execute(arg1, arg2)
}
}
```

There are a few restrictions on `#[auto_impl]` for immutably borrowed references. The trait must:

- Only have methods that take `&self`

There are no restrictions on `#[auto_impl]` for mutably borrowed references.
2 changes: 1 addition & 1 deletion compile_test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ trait FnTrait3 {
fn execute(&mut self);
}

#[auto_impl(Arc, Box, Rc)]
#[auto_impl(Arc, Box, Rc, &, &mut)]
trait RefTrait1<'a, T: for<'b> Into<&'b str>> {
type Type1;
type Type2;
Expand Down
64 changes: 60 additions & 4 deletions src/impl_as_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ use model::*;
///
/// - The smart pointer wraps a single generic value, like `Arc<T>`, `Box<T>`, `Rc<T>`
/// - The smart pointer implements `AsRef<T>`
pub fn build(component: &AutoImpl, ref_ty: Trait) -> Result<Tokens, String> {
let component_ident = &component.ident;

pub fn build_wrapper(component: &AutoImpl, ref_ty: Trait) -> Result<Tokens, String> {
let impl_methods = component.methods.iter()
.map(|method| {
let valid_receiver = match method.arg_self {
Expand All @@ -36,6 +34,64 @@ pub fn build(component: &AutoImpl, ref_ty: Trait) -> Result<Tokens, String> {
})
.collect::<Result<Vec<_>, _>>()?;

build(component, vec![], quote!(#ref_ty < TAutoImpl >), impl_methods)
}

/// Auto implement a trait for an immutable reference.
///
/// This expects the input to have the following properties:
///
/// - All methods have an `&self` receiver
pub fn build_immutable(component: &AutoImpl) -> Result<Tokens, String> {
let impl_methods = component.methods.iter()
.map(|method| {
let valid_receiver = match method.arg_self {
Some(ref arg_self) => match *arg_self {
SelfArg::Ref(_, syn::Mutability::Immutable) => true,
_ => false
},
None => false
};

if !valid_receiver {
Err("auto impl for `&T` is only supported for methods with a `&self` reciever")?
}

method.build_impl_item(|method| {
let fn_ident = &method.ident;
let fn_args = &method.arg_pats;

quote!({
(**self).#fn_ident( #(#fn_args),* )
})
})
})
.collect::<Result<Vec<_>, _>>()?;

build(component, vec![quote!('auto)], quote!(&'auto TAutoImpl), impl_methods)
}

/// Auto implement a trait for a mutable reference.
pub fn build_mutable(component: &AutoImpl) -> Result<Tokens, String> {
let impl_methods = component.methods.iter()
.map(|method| {
method.build_impl_item(|method| {
let fn_ident = &method.ident;
let fn_args = &method.arg_pats;

quote!({
(**self).#fn_ident( #(#fn_args),* )
})
})
})
.collect::<Result<Vec<_>, _>>()?;

build(component, vec![quote!('auto)], quote!(&'auto mut TAutoImpl), impl_methods)
}

fn build(component: &AutoImpl, extra_lifetimes: Vec<Tokens>, impl_ident: Tokens, impl_methods: Vec<syn::TraitItem>) -> Result<Tokens, String> {
let component_ident = &component.ident;

let impl_associated_types = component.associated_types.iter()
.map(|associated_type| {
associated_type.build_impl_item(|associated_type| {
Expand All @@ -49,7 +105,7 @@ pub fn build(component: &AutoImpl, ref_ty: Trait) -> Result<Tokens, String> {
let (trait_tys, impl_lifetimes, impl_tys, where_clauses) = component.split_generics();

Ok(quote!(
impl< #(#impl_lifetimes,)* #(#impl_tys,)* TAutoImpl > #component_ident #trait_tys for #ref_ty < TAutoImpl >
impl< #(#extra_lifetimes,)* #(#impl_lifetimes,)* #(#impl_tys,)* TAutoImpl > #component_ident #trait_tys for #impl_ident
where TAutoImpl: #component_ident #trait_tys
#(,#where_clauses)*
{
Expand Down
113 changes: 90 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ include!("lib.proc_macro.rs");
extern crate quote;
extern crate syn;

mod parse;
mod model;
mod impl_as_ref;
mod impl_fn;
Expand All @@ -16,7 +17,7 @@ use std::str::FromStr;
use quote::Tokens;
use model::*;

const IMPL_FOR_TRAIT_ERR: &'static str = "expected a list containing any of `Arc`, `Rc`, `Box`, `Fn`, `FnMut` or `FnOnce`";
const IMPL_FOR_TRAIT_ERR: &'static str = "expected a list containing any of `&`, `&mut`, `Arc`, `Rc`, `Box`, `Fn`, `FnMut` or `FnOnce`";

#[derive(Debug, PartialEq)]
enum ImplForTrait {
Expand All @@ -26,6 +27,8 @@ enum ImplForTrait {
Fn,
FnMut,
FnOnce,
Ref,
RefMut,
}

impl FromStr for ImplForTrait {
Expand All @@ -39,29 +42,21 @@ impl FromStr for ImplForTrait {
"Fn" => Ok(ImplForTrait::Fn),
"FnMut" => Ok(ImplForTrait::FnMut),
"FnOnce" => Ok(ImplForTrait::FnOnce),
_ => Err(IMPL_FOR_TRAIT_ERR)?
"&" => Ok(ImplForTrait::Ref),
"&mut" => Ok(ImplForTrait::RefMut),
c => {
println!("got: {}", c);
Err(IMPL_FOR_TRAIT_ERR)?
}
}
}
}

fn parse_impl_types(tokens: Tokens) -> Result<Vec<ImplForTrait>, String> {
let attr = syn::parse_outer_attr(tokens.as_ref())?;

let idents: Vec<Result<ImplForTrait, String>> = match attr.value {
syn::MetaItem::Word(ident) => vec![ImplForTrait::from_str(ident.as_ref())],
syn::MetaItem::List(_, idents) => {
idents.into_iter().map(|ident| {
match ident {
syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ident)) => ImplForTrait::from_str(ident.as_ref()),
_ => Err(IMPL_FOR_TRAIT_ERR)?
}
})
.collect()
},
_ => Err(IMPL_FOR_TRAIT_ERR)?
};

idents.into_iter().collect()
parse::attr(tokens.as_str())?
.into_iter()
.map(|ident| ImplForTrait::from_str(&ident))
.collect()
}

fn auto_impl_expand(impl_for_traits: &[ImplForTrait], tokens: Tokens) -> Result<Tokens, String> {
Expand All @@ -71,9 +66,11 @@ fn auto_impl_expand(impl_for_traits: &[ImplForTrait], tokens: Tokens) -> Result<
let impls: Vec<_> = impl_for_traits.iter()
.map(|impl_for_trait| {
match *impl_for_trait {
ImplForTrait::Arc => impl_as_ref::build(&auto_impl, Trait::new("Arc", quote!(::std::sync::Arc))),
ImplForTrait::Rc => impl_as_ref::build(&auto_impl, Trait::new("Rc", quote!(::std::rc::Rc))),
ImplForTrait::Box => impl_as_ref::build(&auto_impl, Trait::new("Box", quote!(Box))),
ImplForTrait::Arc => impl_as_ref::build_wrapper(&auto_impl, Trait::new("Arc", quote!(::std::sync::Arc))),
ImplForTrait::Rc => impl_as_ref::build_wrapper(&auto_impl, Trait::new("Rc", quote!(::std::rc::Rc))),
ImplForTrait::Box => impl_as_ref::build_wrapper(&auto_impl, Trait::new("Box", quote!(Box))),
ImplForTrait::Ref => impl_as_ref::build_immutable(&auto_impl),
ImplForTrait::RefMut => impl_as_ref::build_mutable(&auto_impl),
ImplForTrait::Fn => impl_fn::build(&auto_impl, Trait::new("Fn", quote!(Fn))),
ImplForTrait::FnMut => impl_fn::build(&auto_impl, Trait::new("FnMut", quote!(FnMut))),
ImplForTrait::FnOnce => impl_fn::build(&auto_impl, Trait::new("FnOnce", quote!(FnOnce)))
Expand Down Expand Up @@ -137,7 +134,34 @@ mod tests {

let impls = parse_impl_types(input).unwrap_err();

assert_eq!("expected a list containing any of `Arc`, `Rc`, `Box`, `Fn`, `FnMut` or `FnOnce`", &impls);
assert_eq!("expected a list containing any of `&`, `&mut`, `Arc`, `Rc`, `Box`, `Fn`, `FnMut` or `FnOnce`", &impls);
}

#[test]
fn parse_attr_single() {
let input = quote!(#[auto_impl(&)]);
let impl_types = parse_impl_types(input).unwrap();

let expected = vec![ImplForTrait::Ref];

assert_eq!(impl_types, expected);
}

#[test]
fn parse_attr_multi() {
let input = quote!(#[auto_impl(&, &mut, Arc)]);

println!("{}", input);

let impl_types = parse_impl_types(input).unwrap();

let expected = vec![
ImplForTrait::Ref,
ImplForTrait::RefMut,
ImplForTrait::Arc,
];

assert_eq!(impl_types, expected);
}

#[test]
Expand Down Expand Up @@ -241,6 +265,49 @@ mod tests {
assert_invalid(&[ImplForTrait::Arc], input, "auto impl for `Arc` is only supported for methods with a `&self` reciever");
}

#[test]
fn impl_ref() {
let input = quote!(
/// Some docs.
pub trait ItWorks {
/// Some docs.
fn method1(&self, arg1: i32, arg2: Option<String>) -> Result<(), String>;
}
);

let derive = quote!(
impl<'auto, TAutoImpl> ItWorks for &'auto TAutoImpl
where TAutoImpl: ItWorks
{
fn method1(&self, arg1: i32, arg2: Option<String>) -> Result<(), String> {
(**self).method1(arg1, arg2)
}
}

impl<'auto, TAutoImpl> ItWorks for &'auto mut TAutoImpl
where TAutoImpl: ItWorks
{
fn method1(&self, arg1: i32, arg2: Option<String>) -> Result<(), String> {
(**self).method1(arg1, arg2)
}
}
);

assert_tokens(&[ImplForTrait::Ref, ImplForTrait::RefMut], input, derive);
}

#[test]
fn invalid_ref_mut_method() {
let input = quote!(
pub trait ItWorks {
fn method1(&mut self, arg1: i32, arg2: Option<String>) -> Result<(), String>;
fn method2(&self);
}
);

assert_invalid(&[ImplForTrait::Ref], input, "auto impl for `&T` is only supported for methods with a `&self` reciever");
}

#[test]
fn impl_fn() {
let input = quote!(
Expand Down
53 changes: 53 additions & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::str;

pub fn attr<'a>(input: &'a str) -> Result<Vec<String>, String> {
fn remove_whitespace(i: &[u8]) -> String {
let ident = i.iter().filter(|c| **c != b' ').cloned().collect();
String::from_utf8(ident).expect("non-utf8 string")
}

fn ident(i: &[u8]) -> (String, &[u8]) {
if let Some(end) = i.iter().position(|c| *c == b',') {
(remove_whitespace(&i[..end]), &i[end..])
}
else {
(remove_whitespace(i), &[])
}
}

fn attr_inner<'a>(rest: &'a [u8], traits: &mut Vec<String>) -> Result<(), String> {
match rest.len() {
0 => Ok(()),
_ => {
match rest[0] as char {
'&' => {
let (ident, rest) = ident(rest);
traits.push(ident);

attr_inner(rest, traits)
},
c if c.is_alphabetic() => {
let (ident, rest) = ident(rest);
traits.push(ident);

attr_inner(rest, traits)
},
_ => attr_inner(&rest[1..], traits)
}
}
}
}

let mut traits = vec![];
let input = input.as_bytes();

let open = input.iter().position(|c| *c == b'(');
let close = input.iter().position(|c| *c == b')');

match (open, close) {
(Some(open), Some(close)) => attr_inner(&input[open..close], &mut traits)?,
_ => Err("attribute format should be `#[auto_impl(a, b, c)]`")?
}

Ok(traits)
}