diff --git a/Cargo.toml b/Cargo.toml index fa60aca..c697437 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "auto_impl" version = "0.2.0" -authors = ["Ashley Mannix "] +authors = [ + "Ashley Mannix ", + "Lukas Kalbertodt ", +] license = "MIT" description = "Automatically implement traits for common smart pointers and closures" repository = "https://github.com/KodrAus/auto_impl" @@ -17,5 +20,6 @@ travis-ci = { repository = "KodrAus/auto_impl" } proc-macro = true [dependencies] -quote = "~0.3" -syn = { version = "~0.11", features = ["full"] } +proc-macro2 = { version = "0.4.6", features = ["nightly"] } +quote = "0.6.3" +syn = { version = "0.14.4", features = ["full"] } diff --git a/compile_test/src/main.rs b/compile_test/src/main.rs index c39edb4..a762b95 100644 --- a/compile_test/src/main.rs +++ b/compile_test/src/main.rs @@ -2,7 +2,7 @@ Try running `cargo expand` on this crate to see the output of `#[auto_impl]`. */ -#![feature(proc_macro)] +#![feature(use_extern_macros)] extern crate auto_impl; diff --git a/examples/greet_closure.rs b/examples/greet_closure.rs new file mode 100644 index 0000000..8f3db35 --- /dev/null +++ b/examples/greet_closure.rs @@ -0,0 +1,41 @@ +#![feature(use_extern_macros)] + +extern crate auto_impl; + +use auto_impl::auto_impl; + + +/// This simple trait can be implemented for `Fn` types, but not for `FnMut` or +/// `FnOnce` types. The latter two types require a mutable reference to `self` +/// or a `self` by value to be called, but `greet()` only has an immutable +/// reference. Try creating an auto-impl for `FnMut`: you should get an error. +/// +/// This attribute expands to the following impl (not exactly this code, but +/// equivalent, slightly uglier code): +/// +/// ``` +/// impl Greeter for F { +/// fn greet(&self, name: &str) { +/// self(name) +/// } +/// } +/// ``` +#[auto_impl(Fn)] +trait Greeter { + fn greet(&self, name: &str); +} + + +fn greet_people(greeter: impl Greeter) { + greeter.greet("Anna"); + greeter.greet("Bob"); +} + + +fn main() { + // We can simply pass a closure here, since this specific closure + // implements `Fn(&str)` and therefore also `Greeter`. Note that we need + // explicit type annotations here. This has nothing to do with `auto_impl`, + // but is simply a limitation of type inference. + greet_people(|name: &str| println!("Hallo {} :)", name)); +} diff --git a/examples/refs.rs b/examples/refs.rs new file mode 100644 index 0000000..0845ee9 --- /dev/null +++ b/examples/refs.rs @@ -0,0 +1,64 @@ +#![feature(use_extern_macros)] + +extern crate auto_impl; + +use std::fmt::Display; + +use auto_impl::auto_impl; + + +/// This trait can be implemented for all reference or pointer types: &, &mut, +/// Box, Rc and Arc. +/// +/// This attribute expands to the following impl (not exactly this code, but +/// equivalent, slightly uglier code): +/// +/// ``` +/// impl<'a, T: 'a + DisplayCollection> DisplayCollection for &'a T { +/// type Out = T::Out; +/// fn display_at(&self, index: usize) -> Option<&Self::Out> { +/// (**self).display_at(index) +/// } +/// } +/// +/// impl DisplayCollection for Box { +/// type Out = T::Out; +/// fn display_at(&self, index: usize) -> Option<&Self::Out> { +/// (**self).display_at(index) +/// } +/// } +/// ``` +#[auto_impl(&, Box)] +trait DisplayCollection { + /// If the length is statically known, this is `Some(len)`. + const LEN: Option; + type Out: Display; + fn display_at(&self, index: usize) -> Option<&Self::Out>; +} + +impl DisplayCollection for Vec { + const LEN: Option = None; + type Out = T; + fn display_at(&self, index: usize) -> Option<&Self::Out> { + self.get(index) + } +} + +fn show_first(c: impl DisplayCollection) { + match c.display_at(0) { + Some(x) => println!("First: {}", x), + None => println!("Nothing :/"), + } +} + + +fn main() { + let v = vec!["dog", "cat"]; + let boxed = Box::new(v.clone()); + + show_first(v.clone()); // Vec<&str> (our manual impl) + show_first(&v); // &Vec<&str> (auto-impl) + show_first(&&v); // &&Vec<&str> (works too, of course) + show_first(boxed.clone()); // Box> (auto-impl) + show_first(&boxed); // &Box> +} diff --git a/src/gen.rs b/src/gen.rs new file mode 100644 index 0000000..bf2ddd3 --- /dev/null +++ b/src/gen.rs @@ -0,0 +1,606 @@ +use proc_macro::{Diagnostic, Span}; +use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; +use quote::{ToTokens, TokenStreamExt}; +use syn::{ + FnArg, Ident, ItemTrait, Lifetime, MethodSig, Pat, PatIdent, TraitItem, TraitItemMethod, + TraitItemType, TraitItemConst, +}; + +use crate::{proxy::ProxyType, spanned::Spanned}; + +/// The type parameter used in the proxy type. Usually, one would just use `T`, +/// but this could conflict with type parameters on the trait. +/// +/// Why do we have to care about this? Why about hygiene? In the first version +/// of stable proc_macros, only call site spans are included. That means that +/// we cannot generate spans that do not conflict with any other ident the user +/// wrote. Once proper hygiene is available to proc_macros, this should be +/// changed. +const PROXY_TY_PARAM_NAME: &str = "__AutoImplProxyT"; + +/// The lifetime parameter used in the proxy type if the proxy type is `&` or +/// `&mut`. For more information see `PROXY_TY_PARAM_NAME`. +const PROXY_LT_PARAM_NAME: &str = "'__auto_impl_proxy_lifetime"; + + +/// Generates one complete impl of the given trait for each of the given proxy +/// types. All impls are returned as token stream. +pub(crate) fn gen_impls( + proxy_types: &[ProxyType], + trait_def: &syn::ItemTrait, +) -> Result<::proc_macro::TokenStream, ()> { + let mut tokens = TokenStream2::new(); + + // One impl for each proxy type + for proxy_type in proxy_types { + let header = header(proxy_type, trait_def)?; + let items = gen_items(proxy_type, trait_def)?; + + tokens.append_all(quote! { + #header { #( #items )* } + }); + } + + Ok(tokens.into()) +} + +/// Generates the header of the impl of the given trait for the given proxy +/// type. +fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Result { + let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span2::call_site()); + let proxy_lt_param = &Lifetime::new(PROXY_LT_PARAM_NAME, Span2::call_site()); + + // Generate generics for impl positions from trait generics. + let (impl_generics, trait_generics, where_clause) = trait_def.generics.split_for_impl(); + + // The name of the trait with all generic parameters applied. + let trait_ident = &trait_def.ident; + let trait_path = quote! { #trait_ident #trait_generics }; + + + // Here we assemble the parameter list of the impl (the thing in + // `impl< ... >`). This is simply the parameter list of the trait with + // one or two parameters added. For a trait `trait Foo<'x, 'y, A, B>`, + // it will look like this: + // + // '__auto_impl_proxy_lifetime, 'x, 'y, A, B, __AutoImplProxyT + // + // The `'__auto_impl_proxy_lifetime` in the beginning is only added when + // the proxy type is `&` or `&mut`. + let impl_generics = { + // 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::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 }) + } + }; + + // Append all parameters from the trait. Sadly, `impl_generics` + // includes the angle brackets `< >` so we have to remove them like + // this. + let mut tts = impl_generics.into_token_stream() + .into_iter() + .skip(1) // the opening `<` + .collect::>(); + tts.pop(); // the closing `>` + params.append_all(&tts); + + // Append proxy type parameter (if there aren't any parameters so far, + // we need to add a comma first). + let comma = if params.is_empty() || tts.is_empty() { + quote!{} + } else { + quote! { , } + }; + params.append_all(quote! { #comma #proxy_ty_param #ty_bounds }); + + params + }; + + + // The tokens after `for` in the impl header (the type the trait is + // implemented for). + let self_ty = match *proxy_type { + ProxyType::Ref => quote! { & #proxy_lt_param #proxy_ty_param }, + ProxyType::RefMut => quote! { & #proxy_lt_param mut #proxy_ty_param }, + ProxyType::Arc => quote! { ::std::sync::Arc<#proxy_ty_param> }, + ProxyType::Rc => quote! { ::std::rc::Rc<#proxy_ty_param> }, + ProxyType::Box => quote! { ::std::boxed::Box<#proxy_ty_param> }, + ProxyType::Fn => quote! { #proxy_ty_param }, + ProxyType::FnMut => quote! { #proxy_ty_param }, + ProxyType::FnOnce => quote! { #proxy_ty_param }, + }; + + + // Combine everything + Ok(quote! { + impl<#impl_generics> #trait_path for #self_ty #where_clause + }) +} + +/// Generates the Fn-trait type (e.g. `FnMut(u32) -> String`) for the given +/// trait and proxy type (the latter has to be `Fn`, `FnMut` or `FnOnce`!) +/// +/// If the trait is unsuitable to be implemented for the given proxy type, an +/// error is emitted and `Err(())` is returned. +fn gen_fn_type_for_trait( + proxy_type: &ProxyType, + trait_def: &ItemTrait, +) -> Result { + // Only traits with exactly one method can be implemented for Fn-traits. + // Associated types and consts are also not allowed. + let method = trait_def.items.get(0).and_then(|item| { + if let TraitItem::Method(m) = item { + Some(m) + } else { + None + } + }); + + // If this requirement is not satisfied, we emit an error. + if method.is_none() || trait_def.items.len() > 1 { + return trait_def.span() + .error( + "this trait cannot be auto-implemented for Fn-traits (only traits with exactly \ + one method and no other items are allowed)" + ) + .emit_with_attr_note(); + } + + // We checked for `None` above + let method = method.unwrap(); + let sig = &method.sig; + + + // Check for forbidden modifier of the method + if let Some(const_token) = sig.constness { + return const_token.span() + .error(format!( + "the trait '{}' cannot be auto-implemented for Fn-traits: const methods are not \ + allowed", + trait_def.ident, + )) + .emit_with_attr_note(); + } + + if let Some(unsafe_token) = &sig.unsafety { + return unsafe_token.span() + .error(format!( + "the trait '{}' cannot be auto-implemented for Fn-traits: unsafe methods are not \ + allowed", + trait_def.ident, + )) + .emit_with_attr_note(); + } + + if let Some(abi_token) = &sig.abi { + return abi_token.span() + .error(format!( + "the trait '{}' cannot be implemented for Fn-traits: custom ABIs are not allowed", + trait_def.ident, + )) + .emit_with_attr_note(); + } + + + // ======================================================================= + // Check if the trait can be implemented for the given proxy type + let self_type = SelfType::from_sig(&method.sig); + let err = match (self_type, proxy_type) { + // The method needs to have a receiver + (SelfType::None, _) => Some(("Fn-traits", "no", "")), + + // We can't impl methods with `&mut self` or `&self` receiver for + // `FnOnce` + (SelfType::Mut, ProxyType::FnOnce) => { + Some(("`FnOnce`", "a `&mut self`", " (only `self` is allowed)")) + } + (SelfType::Ref, ProxyType::FnOnce) => { + Some(("`FnOnce`", "a `&self`", " (only `self` is allowed)")) + } + + // We can't impl methods with `&self` receiver for `FnMut` + (SelfType::Ref, ProxyType::FnMut) => Some(( + "`FnMut`", + "a `&self`", + " (only `self` and `&mut self` are allowed)", + )), + + // Other combinations are fine + _ => None, + }; + + if let Some((fn_traits, receiver, allowed)) = err { + let msg = format!( + "the trait '{}' cannot be auto-implemented for {}, because this method has \ + {} receiver{}", + trait_def.ident, + fn_traits, + receiver, + allowed, + ); + + return method.sig.span().error(msg).emit_with_attr_note(); + } + + // ======================================================================= + // Generate the full Fn-type + + // The path to the Fn-trait + let fn_name = match proxy_type { + ProxyType::Fn => quote! { ::std::ops::Fn }, + ProxyType::FnMut => quote! { ::std::ops::FnMut }, + ProxyType::FnOnce => quote! { ::std::ops::FnOnce }, + _ => panic!("internal error in auto_impl (function contract violation)"), + }; + + // The return type + let ret = &sig.decl.output; + + // Now it get's a bit complicated. The types of the function signature + // could contain "local" lifetimes, meaning that they are not declared in + // the trait definition (or are `'static`). We need to extract all local + // lifetimes to declare them with HRTB (e.g. `for<'a>`). + // + // In Rust 2015 that was easy: we could just take the lifetimes explicitly + // declared in the function signature. Those were the local lifetimes. + // Unfortunately, with in-band lifetimes, things get more complicated. We + // need to take a look at all lifetimes inside the types (arbitrarily deep) + // and check if they are local or not. + // + // In cases where lifetimes are omitted (e.g. `&str`), we don't have a + // problem. If we just translate that to `for<> Fn(&str)`, it's fine: all + // omitted lifetimes in an `Fn()` type are automatically declared as HRTB. + // + // TODO: Implement this check for in-band lifetimes! + let local_lifetimes = sig.decl.generics.lifetimes(); + + // The input types as comma separated list. We skip the first argument, as + // this is the receiver argument. + let mut arg_types = TokenStream2::new(); + for arg in sig.decl.inputs.iter().skip(1) { + match arg { + FnArg::Captured(arg) => { + let ty = &arg.ty; + arg_types.append_all(quote! { #ty , }); + } + + // Honestly, I'm not sure what this is. + FnArg::Ignored(_) => { + panic!("unexpected ignored argument (auto_impl is confused)"); + } + + // This can't happen in today's Rust and it's unlikely to change in + // the near future. + FnArg::Inferred(_) => { + panic!("argument with inferred type in trait method"); + } + + // We skipped the receiver already + FnArg::SelfRef(_) | FnArg::SelfValue(_) => {} + } + } + + + Ok(quote! { + for< #(#local_lifetimes),* > #fn_name (#arg_types) #ret + }) +} + +/// Generates the implementation of all items of the given trait. These +/// implementations together are the body of the `impl` block. +fn gen_items( + proxy_type: &ProxyType, + trait_def: &ItemTrait, +) -> Result, ()> { + trait_def.items.iter().map(|item| { + match item { + TraitItem::Const(c) => gen_const_item(proxy_type, c, trait_def), + TraitItem::Method(method) => gen_method_item(proxy_type, method, trait_def), + TraitItem::Type(ty) => gen_type_item(proxy_type, ty, trait_def), + TraitItem::Macro(mac) => { + // We cannot resolve the macro invocation and thus cannot know + // if it adds additional items to the trait. Thus, we have to + // give up. + mac.span() + .error( + "traits with macro invocations in their bodies are not \ + supported by auto_impl" + ) + .emit_with_attr_note() + }, + TraitItem::Verbatim(v) => { + // I don't quite know when this happens, but it's better to + // notify the user with a nice error instead of panicking. + v.span() + .error("unexpected 'verbatim'-item (auto-impl doesn't know how to handle it)") + .emit_with_attr_note() + } + } + }).collect() +} + +/// Generates the implementation of an associated const item described by +/// `item`. The implementation is returned as token stream. +/// +/// If the proxy type is an Fn*-trait, an error is emitted and `Err(())` is +/// returned. +fn gen_const_item( + proxy_type: &ProxyType, + item: &TraitItemConst, + trait_def: &ItemTrait, +) -> Result { + // A trait with associated consts cannot be implemented for Fn* types. + if proxy_type.is_fn() { + return item.span() + .error(format!( + "the trait `{}` cannot be auto-implemented for Fn-traits, because it has \ + associated consts (only traits with a single method can be implemented \ + for Fn-traits)", + trait_def.ident, + )) + .emit_with_attr_note(); + } + + // We simply use the associated const from our type parameter. + let const_name = &item.ident; + let const_ty = &item.ty; + let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span2::call_site()); + + Ok(quote ! { + const #const_name: #const_ty = #proxy_ty_param::#const_name; + }) +} + +/// Generates the implementation of an associated type item described by `item`. +/// The implementation is returned as token stream. +/// +/// If the proxy type is an Fn*-trait, an error is emitted and `Err(())` is +/// returned. +fn gen_type_item( + proxy_type: &ProxyType, + item: &TraitItemType, + trait_def: &ItemTrait, +) -> Result { + // A trait with associated types cannot be implemented for Fn* types. + if proxy_type.is_fn() { + return item.span() + .error(format!( + "the trait `{}` cannot be auto-implemented for Fn-traits, because it has \ + associated types (only traits with a single method can be implemented \ + for Fn-traits)", + trait_def.ident, + )) + .emit_with_attr_note(); + } + + // We simply use the associated type from our type parameter. + let assoc_name = &item.ident; + let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span2::call_site()); + + Ok(quote ! { + type #assoc_name = #proxy_ty_param::#assoc_name; + }) +} + +/// Generates the implementation of a method item described by `item`. The +/// implementation is returned as token stream. +/// +/// This function also performs sanity checks, e.g. whether the proxy type can +/// be used to implement the method. If any error occurs, the error is +/// immediately emitted and `Err(())` is returned. +fn gen_method_item( + proxy_type: &ProxyType, + item: &TraitItemMethod, + trait_def: &ItemTrait, +) -> Result { + // Determine the kind of the method, determined by the self type. + let sig = &item.sig; + let self_arg = SelfType::from_sig(sig); + + // Check self type and proxy type combination + check_receiver_compatible(proxy_type, self_arg, &trait_def.ident, sig.span())?; + + // Generate the list of argument used to call the method. + let args = get_arg_list(sig.decl.inputs.iter())?; + + // Generate the body of the function. This mainly depends on the self type, + // but also on the proxy type. + let name = &sig.ident; + let body = match self_arg { + // Fn proxy types get a special treatment + _ if proxy_type.is_fn() => { + quote! { self(#args) } + } + + // No receiver + SelfType::None => { + // The proxy type is a reference, smartpointer or Box. + let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span2::call_site()); + quote! { #proxy_ty_param::#name(#args) } + } + + // Receiver `self` (by value) + SelfType::Value => { + // The proxy type is a Box. + quote! { (*self).#name(#args) } + } + + // `&self` or `&mut self` receiver + SelfType::Ref | SelfType::Mut => { + // The proxy type could be anything in the `Ref` case, and `&mut` + // or Box in the `Mut` case. + quote! { (**self).#name(#args) } + } + }; + + // Combine body with signature + // TODO: maybe add `#[inline]`? + Ok(quote! { #sig { #body }}) +} + + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum SelfType { + None, + Ref, + Mut, + Value, +} + +impl SelfType { + fn from_sig(sig: &MethodSig) -> Self { + match sig.decl.inputs.iter().next() { + Some(FnArg::SelfValue(_)) => SelfType::Value, + Some(FnArg::SelfRef(arg)) if arg.mutability.is_none() => SelfType::Ref, + Some(FnArg::SelfRef(arg)) if arg.mutability.is_some() => SelfType::Mut, + _ => SelfType::None, + } + } + + fn as_str(&self) -> Option<&'static str> { + match *self { + SelfType::None => None, + SelfType::Ref => Some("&self"), + SelfType::Mut => Some("&mut self"), + SelfType::Value => Some("self"), + } + } +} + +/// Checks if this method can be implemented for the given proxy type. If not, +/// we will emit an error pointing to the method signature. +fn check_receiver_compatible( + proxy_type: &ProxyType, + self_arg: SelfType, + trait_name: &Ident, + sig_span: Span, +) -> Result<(), ()> { + match (proxy_type, self_arg) { + (ProxyType::Ref, SelfType::Mut) + | (ProxyType::Ref, SelfType::Value) => { + sig_span + .error(format!( + "the trait `{}` cannot be auto-implemented for immutable references, because \ + this method has a `{}` receiver (only `&self` and no receiver are \ + allowed)", + trait_name, + self_arg.as_str().unwrap(), + )) + .emit_with_attr_note() + } + + (ProxyType::RefMut, SelfType::Value) => { + sig_span + .error(format!( + "the trait `{}` cannot be auto-implemented for mutable references, because \ + this method has a `self` receiver (only `&self`, `&mut self` and no \ + receiver are allowed)", + trait_name, + )) + .emit_with_attr_note() + } + + (ProxyType::Rc, SelfType::Mut) + | (ProxyType::Rc, SelfType::Value) + | (ProxyType::Arc, SelfType::Mut) + | (ProxyType::Arc, SelfType::Value) => { + let ptr_name = if *proxy_type == ProxyType::Rc { + "Rc" + } else { + "Arc" + }; + + sig_span + .error(format!( + "the trait `{}` cannot be auto-implemented for {}-smartpointer, because \ + this method has a `{}` receiver (only `&self` and no receiver are \ + allowed)", + trait_name, + ptr_name, + self_arg.as_str().unwrap(), + )) + .emit_with_attr_note() + } + + (ProxyType::Fn, _) | (ProxyType::FnMut, _) | (ProxyType::FnOnce, _) => { + // The Fn-trait being compatible with the receiver was already + // checked before (in `gen_fn_type_for_trait()`). + Ok(()) + } + + _ => Ok(()), // All other combinations are fine + } +} + +/// Generates a list of comma-separated arguments used to call the function. +/// Currently, only simple names are valid and more complex pattern will lead +/// to an error being emitted. `self` parameters are ignored. +fn get_arg_list(inputs: impl Iterator) -> Result { + let mut args = TokenStream2::new(); + + for arg in inputs { + match arg { + FnArg::Captured(arg) => { + // Make sure the argument pattern is a simple name. In + // principle, we could probably support patterns, but it's + // not that important now. + if let Pat::Ident(PatIdent { + by_ref: None, + mutability: None, + ident, + subpat: None, + }) = &arg.pat + { + // Add name plus trailing comma to tokens + args.append_all(quote! { #ident , }); + } else { + return arg.pat.span() + .error( + "argument patterns are not supported by #[auto-impl]. Please use \ + a simple name (not `_`)." + ) + .emit_with_attr_note(); + } + } + + // Honestly, I'm not sure what this is. + FnArg::Ignored(_) => { + panic!("ignored argument encountered (auto_impl is confused)"); + } + + // This can't happen in today's Rust and it's unlikely to change in + // the near future. + FnArg::Inferred(_) => { + panic!("argument with inferred type in trait method"); + } + + // There is only one such argument. We handle it elsewhere and + // can ignore it here. + FnArg::SelfRef(_) | FnArg::SelfValue(_) => {} + } + } + + Ok(args) +} + +trait DiagnosticExt { + /// Helper function to add a note to the diagnostic (with a span pointing + /// to the `auto_impl` attribute) and emit the error. Additionally, + /// `Err(())` is always returned. + fn emit_with_attr_note(self) -> Result; +} + +impl DiagnosticExt for Diagnostic { + fn emit_with_attr_note(self) -> Result { + self.span_note(Span::call_site(), "auto-impl requested here") + .emit(); + + Err(()) + } +} diff --git a/src/impl_as_ref.rs b/src/impl_as_ref.rs deleted file mode 100644 index 573ee99..0000000 --- a/src/impl_as_ref.rs +++ /dev/null @@ -1,206 +0,0 @@ -use std::fmt; -use syn; -use quote::Tokens; -use model::*; - -pub struct WrapperTrait { - inner: Trait, - valid_receivers: ValidReceivers, -} - -impl WrapperTrait { - pub fn impl_rc() -> Self { - WrapperTrait { - inner: Trait::new("Rc", quote!(::std::rc::Rc)), - valid_receivers: ValidReceivers { - ref_self: true, - no_self: true, - ..Default::default() - }, - } - } - - pub fn impl_arc() -> Self { - WrapperTrait { - inner: Trait::new("Arc", quote!(::std::sync::Arc)), - valid_receivers: ValidReceivers { - ref_self: true, - no_self: true, - ..Default::default() - }, - } - } - - pub fn impl_box() -> Self { - WrapperTrait { - inner: Trait::new("Box", quote!(::std::boxed::Box)), - valid_receivers: ValidReceivers { - ref_self: true, - ref_mut_self: true, - value_self: true, - no_self: true, - }, - } - } -} - -pub struct RefTrait { - inner: Trait, - valid_receivers: ValidReceivers, -} - -impl RefTrait { - pub fn impl_ref() -> Self { - RefTrait { - inner: Trait::new("&T", quote!(&'auto)), - valid_receivers: ValidReceivers { - ref_self: true, - no_self: true, - ..Default::default() - }, - } - } - - pub fn impl_ref_mut() -> Self { - RefTrait { - inner: Trait::new("&mut T", quote!(&'auto mut)), - valid_receivers: ValidReceivers { - ref_self: true, - ref_mut_self: true, - no_self: true, - ..Default::default() - }, - } - } -} - -/// Auto implement a trait for a smart pointer. -/// -/// This expects the input type to have the following properties: -/// -/// - The smart pointer wraps a single generic value, like `Arc`, `Rc` -/// - The smart pointer implements `AsRef` -pub fn build_wrapper(component: &AutoImpl, ref_ty: WrapperTrait) -> Result { - let inner = ref_ty.inner; - let impl_ident = quote!(#inner < TAutoImpl >); - - build(inner, ref_ty.valid_receivers, vec![], component, impl_ident) -} - -/// 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_ref(component: &AutoImpl, ref_ty: RefTrait) -> Result { - let inner = ref_ty.inner; - let impl_ident = quote!(#inner TAutoImpl); - - build(inner, ref_ty.valid_receivers, vec![quote!('auto)], component, impl_ident) -} - -#[derive(Default)] -struct ValidReceivers { - ref_self: bool, - ref_mut_self: bool, - value_self: bool, - no_self: bool, -} - -impl fmt::Display for ValidReceivers { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut valid_receivers = vec![]; - - if self.ref_self { - valid_receivers.push("`&self`"); - } - if self.ref_mut_self { - valid_receivers.push("`&mut self`"); - } - if self.value_self { - valid_receivers.push("`self`"); - } - if self.no_self { - valid_receivers.push("static"); - } - - match valid_receivers.len() { - 0 => unreachable!(), - 1 => { - write!(f, "{}", valid_receivers[0]) - } - n => { - let first = &valid_receivers[..n - 1].join(", "); - let last = &valid_receivers[n - 1]; - - write!(f, "{} or {}", first, last) - } - } - } -} - -fn build(ref_ty: Trait, valid_receivers: ValidReceivers, extra_lifetimes: Vec, component: &AutoImpl, impl_ident: Tokens) -> Result { - let component_ident = &component.ident; - - 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) => valid_receivers.ref_self, - SelfArg::Ref(_, syn::Mutability::Mutable) => valid_receivers.ref_mut_self, - SelfArg::Value(_) => valid_receivers.value_self, - }, - None => valid_receivers.no_self - }; - - if !valid_receiver { - Err(format!("auto impl for `{}` is only supported for methods with a {} reciever", ref_ty, valid_receivers))? - } - - method.build_impl_item(|method| { - let fn_ident = &method.ident; - let fn_args = &method.arg_pats; - - match method.arg_self { - Some(ref arg_self) => match *arg_self { - // `&self` or `&mut self` - SelfArg::Ref(_, _) => quote!({ - (**self).#fn_ident( #(#fn_args),* ) - }), - // `self` - _ => quote!({ - (*self).#fn_ident( #(#fn_args),* ) - }) - }, - // No `self` - None => quote!({ - TAutoImpl :: #fn_ident( #(#fn_args),* ) - }) - } - }) - }) - .collect::, _>>()?; - - let impl_associated_types = component.associated_types.iter() - .map(|associated_type| { - associated_type.build_impl_item(|associated_type| { - let ty_ident = &associated_type.ident; - - quote!(TAutoImpl :: #ty_ident) - }) - }) - .collect::, _>>()?; - - let (trait_tys, impl_lifetimes, impl_tys, where_clauses) = component.split_generics(); - - Ok(quote!( - impl< #(#extra_lifetimes,)* #(#impl_lifetimes,)* #(#impl_tys,)* TAutoImpl > #component_ident #trait_tys for #impl_ident - where TAutoImpl: #component_ident #trait_tys - #(,#where_clauses)* - { - #(#impl_associated_types)* - - #(#impl_methods)* - } - )) -} diff --git a/src/impl_fn.rs b/src/impl_fn.rs deleted file mode 100644 index b50d7fa..0000000 --- a/src/impl_fn.rs +++ /dev/null @@ -1,139 +0,0 @@ -use syn; -use quote::Tokens; -use model::*; - -pub struct FnTrait { - inner: Trait, -} - -impl FnTrait { - pub fn impl_fn() -> Self { - FnTrait { - inner: Trait::new("Fn", quote!(::std::ops::Fn)) - } - } - - pub fn impl_fn_mut() -> Self { - FnTrait { - inner: Trait::new("FnMut", quote!(::std::ops::FnMut)) - } - } - - pub fn impl_fn_once() -> Self { - FnTrait { - inner: Trait::new("FnOnce", quote!(::std::ops::FnOnce)) - } - } -} - -/// Auto implement a trait for a function. -/// -/// This expects the input type to have the following properties: -/// -/// - It is a function trait that supports the `Fn(input) -> output` syntax sugar -/// -/// This expects the input to have the following properties: -/// -/// - It has a single method -/// - It has no associated type -/// - It has no non-static lifetimes in the return type -pub fn build(component: &AutoImpl, ref_ty: FnTrait) -> Result { - let ref_ty = ref_ty.inner; - - let method = expect_single_method(component, &ref_ty)?; - - expect_static_lifetimes_in_return_ty(&method, &ref_ty)?; - - if component.associated_types.len() > 0 { - Err(format!("auto impl for `{}` is not supported for associated types", ref_ty))? - } - - let component_ident = &component.ident; - - let fn_arg_tys = method.anonymous_arg_lifetimes(); - let fn_output = &method.output; - - let impl_method = method - .build_impl_item(|method| { - let fn_args = &method.arg_pats; - - quote!({ - self( #(#fn_args),* ) - }) - })?; - - let return_ty = method.output.clone().map(|_| { - quote!(-> #fn_output) - }); - - let (trait_tys, impl_lifetimes, impl_tys, where_clauses) = component.split_generics(); - - Ok(quote!( - impl< #(#impl_lifetimes,)* #(#impl_tys,)* TFn> #component_ident #trait_tys for TFn - where TFn: #ref_ty ( #(#fn_arg_tys),* ) #return_ty - #(,#where_clauses)* - { - #impl_method - } - )) -} - -fn expect_static_lifetimes_in_return_ty(method: &AutoImplMethod, ref_ty: &Trait) -> Result<(), String> { - fn is_static(lifetime: &syn::Lifetime) -> bool { - lifetime.ident.as_ref() == "'static" - } - - fn only_static_lifetimes(ty: &syn::Ty) -> bool { - match *ty { - syn::Ty::Slice(ref ty) => only_static_lifetimes(&ty), - syn::Ty::Array(ref ty, _) => only_static_lifetimes(&ty), - syn::Ty::Ptr(ref mut_ty) => only_static_lifetimes(&mut_ty.ty), - syn::Ty::Tup(ref tys) => tys.iter().all(only_static_lifetimes), - syn::Ty::Paren(ref ty) => only_static_lifetimes(&ty), - syn::Ty::Path(_, ref path) => { - path.segments.iter().all(|segment| { - match segment.parameters { - syn::PathParameters::AngleBracketed(ref params) => { - params.lifetimes.iter().all(is_static) && params.types.iter().all(only_static_lifetimes) - }, - syn::PathParameters::Parenthesized(ref params) => { - let output_is_static = match params.output { - Some(ref ty) => only_static_lifetimes(ty), - _ => true - }; - - params.inputs.iter().all(only_static_lifetimes) && output_is_static - } - } - }) - }, - syn::Ty::Rptr(ref lifetime, ref mut_ty) => { - let is_static = match lifetime { - &Some(ref l) => is_static(l), - _ => false - }; - - is_static && only_static_lifetimes(&mut_ty.ty) - }, - _ => true - } - } - - if let Some(ref ty) = method.output { - if !only_static_lifetimes(ty) { - Err(format!("auto impl for `{}` is not supported for non-static lifetimes in return types", ref_ty))? - } - } - - Ok(()) -} - -fn expect_single_method<'a>(component: &'a AutoImpl, ref_ty: &Trait) -> Result<&'a AutoImplMethod, String> { - if component.methods.len() != 1 { - Err(format!("auto impl for `{}` is only supported for traits with 1 method", ref_ty))? - } - - let method = component.methods.iter().next().expect(""); - - Ok(method) -} diff --git a/src/lib.proc_macro.rs b/src/lib.proc_macro.rs deleted file mode 100644 index 813c2e6..0000000 --- a/src/lib.proc_macro.rs +++ /dev/null @@ -1,25 +0,0 @@ -extern crate proc_macro; - -use proc_macro::TokenStream; - -#[proc_macro_attribute] -pub fn auto_impl(attrs: TokenStream, proc_tokens: TokenStream) -> TokenStream { - fn expand(attrs: TokenStream, proc_tokens: TokenStream) -> Result { - let mut tokens = Tokens::new(); - tokens.append(proc_tokens.to_string()); - - let mut attr_tokens = Tokens::new(); - attr_tokens.append(format!("#[auto_impl{}]", attrs.to_string())); - - let impl_types = parse_impl_types(attr_tokens)?; - - let tokens = auto_impl_expand(&impl_types, tokens)?; - - TokenStream::from_str(&tokens.to_string()).map_err(|e| format!("{:?}", e)) - } - - match expand(attrs, proc_tokens) { - Ok(tokens) => tokens, - Err(e) => panic!("auto impl error: {}", e) - } -} diff --git a/src/lib.rs b/src/lib.rs index 7f4bf43..9c83b14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,520 +1,63 @@ -#![recursion_limit="128"] -#![cfg_attr(not(test), feature(proc_macro))] +//! A proc-macro attribute for automatically implementing a trait for +//! references, some common smart pointers and closures. -#[cfg(not(test))] -include!("lib.proc_macro.rs"); +#![feature(crate_in_paths)] +#![feature(extern_prelude)] +#![feature(in_band_lifetimes)] +#![feature(proc_macro_span)] +#![feature(proc_macro_diagnostic)] + +extern crate proc_macro; +extern crate proc_macro2; #[macro_use] extern crate quote; extern crate syn; -mod parse; -mod model; -mod impl_as_ref; -mod impl_fn; - -use std::str::FromStr; -use quote::Tokens; -use impl_as_ref::{RefTrait, WrapperTrait}; -use impl_fn::FnTrait; -use model::*; - -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 { - Arc, - Rc, - Box, - Fn, - FnMut, - FnOnce, - Ref, - RefMut, -} - -impl FromStr for ImplForTrait { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "Arc" => Ok(ImplForTrait::Arc), - "Rc" => Ok(ImplForTrait::Rc), - "Box" => Ok(ImplForTrait::Box), - "Fn" => Ok(ImplForTrait::Fn), - "FnMut" => Ok(ImplForTrait::FnMut), - "FnOnce" => Ok(ImplForTrait::FnOnce), - "&" => Ok(ImplForTrait::Ref), - "&mut" => Ok(ImplForTrait::RefMut), - _ => Err(IMPL_FOR_TRAIT_ERR)?, - } - } -} - -fn parse_impl_types(tokens: Tokens) -> Result, String> { - 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 { - let item = syn::parse_item(tokens.as_ref())?; - let auto_impl = AutoImpl::try_parse(item)?; - - let impls: Vec<_> = impl_for_traits.iter() - .map(|impl_for_trait| { - match *impl_for_trait { - ImplForTrait::Arc => impl_as_ref::build_wrapper(&auto_impl, WrapperTrait::impl_arc()), - ImplForTrait::Rc => impl_as_ref::build_wrapper(&auto_impl, WrapperTrait::impl_rc()), - ImplForTrait::Box => impl_as_ref::build_wrapper(&auto_impl, WrapperTrait::impl_box()), - ImplForTrait::Ref => impl_as_ref::build_ref(&auto_impl, RefTrait::impl_ref()), - ImplForTrait::RefMut => impl_as_ref::build_ref(&auto_impl, RefTrait::impl_ref_mut()), - ImplForTrait::Fn => impl_fn::build(&auto_impl, FnTrait::impl_fn()), - ImplForTrait::FnMut => impl_fn::build(&auto_impl, FnTrait::impl_fn_mut()), - ImplForTrait::FnOnce => impl_fn::build(&auto_impl, FnTrait::impl_fn_once()) +use proc_macro::{Diagnostic, Level, Span, TokenStream}; + +mod gen; +mod proxy; +mod spanned; + + +/// See crate documentation for more information. +#[proc_macro_attribute] +pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream { + // We use the closure trick to catch errors until the `catch` syntax is + // available. If an error occurs, we won't modify or add any tokens. + let impls = || -> Result { + // Try to parse the token stream from the attribute to get a list of + // proxy types. + let proxy_types = proxy::parse_types(args)?; + + // Try to parse the item the `#[auto_impl]` attribute was applied to as + // trait. Unfortunately, `parse()` consume the token stream, so we have + // to clone it. + match syn::parse::(input.clone()) { + // The attribute was applied to a valid trait. Now it's time to + // execute the main step: generate a token stream which contains an + // impl of the trait for each proxy type. + Ok(trait_def) => Ok(gen::gen_impls(&proxy_types, &trait_def)?), + + // If the token stream could not be parsed as trait, this most + // likely means that the attribute was applied to a non-trait item. + // Even if the trait definition was syntactically incorrect, the + // compiler usually does some kind of error recovery to proceed. We + // get the recovered tokens. + Err(e) => { + let msg = "couldn't parse trait item"; + Diagnostic::spanned(Span::call_site(), Level::Error, msg) + .note(e.to_string()) + .note("the #[auto_impl] attribute can only be applied to traits!") + .emit(); + + Err(()) } - }) - .collect(); - - if let Some(err) = impls.iter().find(|res| res.is_err()) { - let err = err.clone().unwrap_err(); - return Err(err) - } - - let impls: Vec<_> = impls.into_iter() - .filter_map(Result::ok) - .collect(); - - Ok(quote!( - #tokens - - #(#impls)* - )) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn assert_tokens(impl_for_traits: &[ImplForTrait], input: Tokens, derive: Tokens) { - let mut expected = Tokens::new(); - expected.append_all(&[&input, &derive]); - - let actual = auto_impl_expand(impl_for_traits, input).unwrap(); - - assert_eq!(expected.to_string(), actual.to_string()); - } - - fn assert_invalid(impl_for_traits: &[ImplForTrait], input: Tokens, err: &'static str) { - let actual_err = auto_impl_expand(impl_for_traits, input).unwrap_err(); - - assert_eq!(err, &actual_err); - } - - #[test] - fn impl_types() { - let input = quote!(#[auto_impl(&, &mut, Rc, Arc, Box, Fn, FnMut, FnOnce)]); - - let impls = parse_impl_types(input).unwrap(); - - assert_eq!(vec![ - ImplForTrait::Ref, - ImplForTrait::RefMut, - ImplForTrait::Rc, - ImplForTrait::Arc, - ImplForTrait::Box, - ImplForTrait::Fn, - ImplForTrait::FnMut, - ImplForTrait::FnOnce - ], impls); - } - - #[test] - fn invalid_impl_types() { - let input = quote!(#[auto_impl(NotSupported)]); - - let impls = parse_impl_types(input); - - assert!(impls.is_err()); - } - - #[test] - fn parse_attr_raw_single() { - let input = "#[auto_impl(&)]"; - let parsed = parse::attr(input).unwrap(); - - assert_eq!(parsed, &["&"]); - } - - #[test] - fn parse_attr_raw() { - let input = "#[auto_impl(&, &mut, Arc)]"; - let parsed = parse::attr(input).unwrap(); - - assert_eq!(parsed, &["&", "&mut", "Arc"]); - } - - #[test] - fn impl_as_ref() { - let input = quote!( - /// Some docs. - pub trait ItWorks { - /// Some docs. - fn method1(&self, arg1: i32, arg2: Option) -> Result<(), String>; - /// Some docs. - fn method2(&self) { - println!("default"); - } - /// Some docs. - fn method3() -> &'static str; - } - ); - - let derive = quote!( - impl ItWorks for ::std::sync::Arc - where TAutoImpl: ItWorks - { - fn method1(&self, arg1: i32, arg2: Option) -> Result<(), String> { - (**self).method1(arg1, arg2) - } - fn method2(&self) { - (**self).method2() - } - fn method3() -> &'static str { - TAutoImpl::method3() - } - } - - impl ItWorks for ::std::rc::Rc - where TAutoImpl: ItWorks - { - fn method1(&self, arg1: i32, arg2: Option) -> Result<(), String> { - (**self).method1(arg1, arg2) - } - fn method2(&self) { - (**self).method2() - } - fn method3() -> &'static str { - TAutoImpl::method3() - } - } - ); - - assert_tokens(&[ImplForTrait::Arc, ImplForTrait::Rc], input, derive); - } - - #[test] - fn impl_box() { - let input = quote!( - /// Some docs. - pub trait ItWorks { - /// Some docs. - fn method1(&self, arg1: i32, arg2: Option) -> Result<(), String>; - /// Some docs. - fn method2(&mut self, arg1: i32, arg2: Option) -> Result<(), String>; - /// Some docs. - fn method3(self, arg1: i32, arg2: Option) -> Result<(), String>; - /// Some docs. - fn method4() -> &'static str; - } - ); - - let derive = quote!( - impl ItWorks for ::std::boxed::Box - where TAutoImpl: ItWorks - { - fn method1(&self, arg1: i32, arg2: Option) -> Result<(), String> { - (**self).method1(arg1, arg2) - } - fn method2(&mut self, arg1: i32, arg2: Option) -> Result<(), String> { - (**self).method2(arg1, arg2) - } - fn method3(self, arg1: i32, arg2: Option) -> Result<(), String> { - (*self).method3(arg1, arg2) - } - fn method4() -> &'static str { - TAutoImpl::method4() - } - } - ); - - assert_tokens(&[ImplForTrait::Box], input, derive); - } - - #[test] - fn impl_as_ref_associated_types() { - let input = quote!( - /// Some docs. - pub trait ItWorks { - /// Some docs. - type Type1: AsRef<[u8]>; - } - ); - - let derive = quote!( - impl ItWorks for ::std::sync::Arc - where TAutoImpl: ItWorks - { - type Type1 = TAutoImpl::Type1; - } - ); - - assert_tokens(&[ImplForTrait::Arc], input, derive); - } - - #[test] - fn invalid_as_ref_mut_method() { - let input = quote!( - pub trait ItWorks { - fn method1(&mut self, arg1: i32, arg2: Option) -> Result<(), String>; - } - ); - - assert_invalid(&[ImplForTrait::Arc], input, "auto impl for `Arc` is only supported for methods with a `&self` or static reciever"); - } - - #[test] - fn invalid_as_ref_by_value_method() { - let input = quote!( - pub trait ItWorks { - fn method1(self, arg1: i32, arg2: Option) -> Result<(), String>; - } - ); - - assert_invalid(&[ImplForTrait::Arc], input, "auto impl for `Arc` is only supported for methods with a `&self` or static reciever"); - } - - #[test] - fn impl_ref() { - let input = quote!( - /// Some docs. - pub trait ItWorks { - /// Some docs. - fn method1(&self, arg1: i32, arg2: Option) -> Result<(), String>; - } - ); - - let derive = quote!( - impl<'auto, TAutoImpl> ItWorks for &'auto TAutoImpl - where TAutoImpl: ItWorks - { - fn method1(&self, arg1: i32, arg2: Option) -> Result<(), String> { - (**self).method1(arg1, arg2) - } - } - - impl<'auto, TAutoImpl> ItWorks for &'auto mut TAutoImpl - where TAutoImpl: ItWorks - { - fn method1(&self, arg1: i32, arg2: Option) -> 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 ItFails { - fn method1(&mut self, arg1: i32, arg2: Option) -> Result<(), String>; - } - ); - - assert_invalid(&[ImplForTrait::Ref], input, "auto impl for `&T` is only supported for methods with a `&self` or static reciever"); - } - - #[test] - fn invalid_ref_by_value_method() { - let input = quote!( - pub trait ItFails { - fn method1(self, arg1: i32, arg2: Option) -> Result<(), String>; - } - ); - - assert_invalid(&[ImplForTrait::Ref], input, "auto impl for `&T` is only supported for methods with a `&self` or static reciever"); - } - - #[test] - fn invalid_ref_mut_by_value_method() { - let input = quote!( - pub trait ItFails { - fn method1(self, arg1: i32, arg2: Option) -> Result<(), String>; - } - ); - - assert_invalid(&[ImplForTrait::RefMut], input, "auto impl for `&mut T` is only supported for methods with a `&self`, `&mut self` or static reciever"); - } - - #[test] - fn impl_fn() { - let input = quote!( - /// Some docs. - pub trait ItWorks { - /// Some docs. - fn method(&self, arg1: i32, arg2: Option) -> Result<&'static str, String>; - } - ); - - let derive = quote!( - impl ItWorks for TFn - where TFn: ::std::ops::Fn(i32, Option) -> Result<&'static str, String> - { - fn method(&self, arg1: i32, arg2: Option) -> Result<&'static str, String> { - self(arg1, arg2) - } - } - ); - - assert_tokens(&[ImplForTrait::Fn], input, derive); - } - - #[test] - fn impl_fn_generics() { - let input = quote!( - /// Some docs. - pub trait ItWorks<'a, T, U> where U: AsRef<[u8]> { - /// Some docs. - fn method<'b>(&'a self, arg1: i32, arg2: &'b U, arg3: &'static str) -> Result; - } - ); - - let derive = quote!( - impl<'a, T, U, TFn> ItWorks<'a, T, U> for TFn - where TFn: ::std::ops::Fn(i32, &U, &'static str) -> Result, - U: AsRef<[u8]> - { - fn method<'b>(&'a self, arg1: i32, arg2: &'b U, arg3: &'static str) -> Result { - self(arg1, arg2, arg3) - } - } - ); - - assert_tokens(&[ImplForTrait::Fn], input, derive); - } - - #[test] - fn impl_fn_mut() { - let input = quote!( - pub trait ItWorks { - fn method(&mut self, arg1: i32, arg2: Option) -> Result<(), String>; - } - ); - - let derive = quote!( - impl ItWorks for TFn - where TFn: ::std::ops::FnMut(i32, Option) -> Result<(), String> - { - fn method(&mut self, arg1: i32, arg2: Option) -> Result<(), String> { - self(arg1, arg2) - } - } - ); - - assert_tokens(&[ImplForTrait::FnMut], input, derive); - } - - #[test] - fn impl_fn_once() { - let input = quote!( - pub trait ItWorks { - fn method(self, arg1: i32, arg2: Option) -> Result<(), String>; - } - ); - - let derive = quote!( - impl ItWorks for TFn - where TFn: ::std::ops::FnOnce(i32, Option) -> Result<(), String> - { - fn method(self, arg1: i32, arg2: Option) -> Result<(), String> { - self(arg1, arg2) - } - } - ); - - assert_tokens(&[ImplForTrait::FnOnce], input, derive); - } - - #[test] - fn impl_fn_no_return() { - let input = quote!( - pub trait ItWorks { - fn method(&self, arg1: i32, arg2: Option); - } - ); - - let derive = quote!( - impl ItWorks for TFn - where TFn: ::std::ops::Fn(i32, Option) - { - fn method(&self, arg1: i32, arg2: Option) { - self(arg1, arg2) - } - } - ); - - assert_tokens(&[ImplForTrait::Fn], input, derive); - } - - #[test] - fn invalid_fn_associated_types() { - let input = quote!( - pub trait ItFails { - type TypeA; - type TypeB; - - fn method(&self); - } - ); - - assert_invalid(&[ImplForTrait::Fn], input, "auto impl for `Fn` is not supported for associated types"); - } - - #[test] - fn invalid_fn_lifetime_in_return_type_path() { - let input = quote!( - pub trait ItFails { - fn method<'a>(&'a self) -> Result, String>; - } - ); - - assert_invalid(&[ImplForTrait::Fn], input, "auto impl for `Fn` is not supported for non-static lifetimes in return types"); - } - - #[test] - fn invalid_fn_lifetime_in_return_type_tuple() { - let input = quote!( - pub trait ItFails { - fn method<'a>(&'a self) -> Result<(&'a str, i32), String>; - } - ); - - assert_invalid(&[ImplForTrait::Fn], input, "auto impl for `Fn` is not supported for non-static lifetimes in return types"); - } - - #[test] - fn invalid_fn_no_methods() { - let input = quote!( - pub trait ItFails { - - } - ); - - assert_invalid(&[ImplForTrait::Fn], input, "auto impl for `Fn` is only supported for traits with 1 method"); - } - - #[test] - fn invalid_fn_multiple_methods() { - let input = quote!( - pub trait ItFails { - fn method1(&self, arg1: i32, arg2: Option) -> Result<(), String>; - fn method2(&self) -> String; - } - ); + } + }().unwrap_or(TokenStream::new()); // If an error occured, we don't add any tokens. - assert_invalid(&[ImplForTrait::Fn], input, "auto impl for `Fn` is only supported for traits with 1 method"); - } + // Combine the original token stream with the additional one containing the + // generated impls (or nothing if an error occured). + vec![input, impls].into_iter().collect() } diff --git a/src/model.rs b/src/model.rs deleted file mode 100644 index 40ba838..0000000 --- a/src/model.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::fmt; -use syn; -use quote::{Tokens, ToTokens}; - -pub struct Trait { - ident: &'static str, - tokens: Tokens, -} - -impl Trait { - pub fn new(ident: &'static str, tokens: Tokens) -> Self { - Trait { - ident: ident, - tokens: tokens - } - } -} - -impl fmt::Display for Trait { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.ident) - } -} - -impl ToTokens for Trait { - fn to_tokens(&self, tokens: &mut Tokens) { - tokens.append(&self.tokens); - } -} - -#[derive(Clone)] -struct Item { - ident: syn::Ident, - attrs: Vec, -} - -pub struct AutoImpl { - pub ident: syn::Ident, - generics: syn::Generics, - pub associated_types: Vec, - pub methods: Vec, -} - -pub enum SelfArg { - Ref(Option, syn::Mutability), - Value(syn::Mutability), -} - -pub struct AutoImplMethod { - pub ident: syn::Ident, - pub arg_self: Option, - pub arg_pats: Vec, - pub arg_tys: Vec, - pub output: Option, - item_impl_factory: Box Result>, -} - -pub struct AutoImplAssociatedType { - pub ident: syn::Ident, - item_impl_factory: Box Result>, -} - -impl AutoImpl { - pub fn try_parse(item: syn::Item) -> Result { - match item.node { - syn::ItemKind::Trait(_, generics, _, items) => { - let mut methods = Vec::new(); - let mut associated_types = Vec::new(); - - for item in items { - let node = item.node; - - let item = Item { - ident: item.ident, - attrs: item.attrs - }; - - match node { - syn::TraitItemKind::Method(sig, _) => { - let method = AutoImplMethod::try_parse(item, sig)?; - methods.push(method); - }, - syn::TraitItemKind::Type(_, _) => { - let ty = AutoImplAssociatedType::try_parse(item)?; - associated_types.push(ty); - }, - _ => Err("only methods and associated types are supported")? - } - } - - Ok(AutoImpl { - ident: item.ident, - generics: generics, - associated_types: associated_types, - methods: methods - }) - }, - _ => Err("expected a `trait`")? - } - } - - pub fn split_generics<'a>(&'a self) -> (syn::TyGenerics<'a>, &[syn::LifetimeDef], &[syn::TyParam], &[syn::WherePredicate]) { - let (_, trait_tys, _) = self.generics.split_for_impl(); - let impl_lifetimes = &self.generics.lifetimes; - let impl_tys = &self.generics.ty_params; - let where_clauses = &self.generics.where_clause.predicates; - - (trait_tys, impl_lifetimes, impl_tys, where_clauses) - } -} - -impl AutoImplMethod { - fn try_parse(item: Item, sig: syn::MethodSig) -> Result { - let ident = item.ident.clone(); - let mut arg_self = None; - let mut arg_pats = Vec::new(); - let mut arg_tys = Vec::new(); - - for arg in &sig.decl.inputs { - match *arg { - syn::FnArg::SelfRef(ref lifetimes, ref mutability) => { - arg_self = Some(SelfArg::Ref(lifetimes.clone(), mutability.clone())); - }, - syn::FnArg::SelfValue(ref mutability) => { - arg_self = Some(SelfArg::Value(mutability.clone())); - } - syn::FnArg::Captured(ref pat, ref ty) => { - arg_pats.push(pat.clone()); - arg_tys.push(ty.clone()); - }, - _ => Err("expected self or ident arg")? - } - } - - let output = match sig.decl.output { - syn::FunctionRetTy::Ty(ref ty) => Some(ty.clone()), - _ => None - }; - - let item_impl_factory = move |block| { - let sig = sig.clone(); - let item = item.clone(); - - let node = syn::TraitItemKind::Method(sig, Some(block)); - - Ok(syn::TraitItem { - ident: item.ident, - attrs: vec![], - node: node, - }) - }; - - Ok(AutoImplMethod { - ident: ident, - item_impl_factory: Box::new(item_impl_factory), - arg_pats: arg_pats, - arg_tys: arg_tys, - arg_self: arg_self, - output: output - }) - } - - pub fn build_impl_item(&self, block_factory: F) -> Result - where F: Fn(&Self) -> Tokens - { - let block_tokens = block_factory(&self); - - let block_expr = syn::parse_expr(&block_tokens.to_string())?; - - let block = match block_expr.node { - syn::ExprKind::Block(_, block) => block, - _ => Err("expected a block")? - }; - - (self.item_impl_factory)(block) - } - - pub fn anonymous_arg_lifetimes(&self) -> Vec { - self.arg_tys.iter() - .map(|ty| { - match *ty { - syn::Ty::Rptr(ref lifetime, ref ty) => { - let ty = ty.clone(); - - let lifetime = match lifetime { - &Some(ref l) if l.ident.as_ref() == "'static" => lifetime.to_owned(), - _ => None - }; - - syn::Ty::Rptr(lifetime, ty) - }, - ref ty @ _ => ty.clone() - } - }) - .collect() - } -} - -impl AutoImplAssociatedType { - fn try_parse(item: Item) -> Result { - let ident = item.ident.clone(); - let item_impl_factory = move |ty| { - let item = item.clone(); - - let node = syn::TraitItemKind::Type(vec![], Some(ty)); - - Ok(syn::TraitItem { - ident: item.ident, - attrs: vec![], - node: node, - }) - }; - - Ok(AutoImplAssociatedType { - ident: ident, - item_impl_factory: Box::new(item_impl_factory), - }) - } - - pub fn build_impl_item(&self, ty_factory: F) -> Result - where F: Fn(&Self) -> Tokens - { - let ty_tokens = ty_factory(&self); - - let ty = syn::parse_type(&ty_tokens.to_string())?; - - (self.item_impl_factory)(ty) - } -} \ No newline at end of file diff --git a/src/parse.rs b/src/parse.rs deleted file mode 100644 index 223ffe3..0000000 --- a/src/parse.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::str; - -pub fn attr<'a>(input: &'a str) -> Result, 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) -> Result<(), String> { - match rest.len() { - 0 => Ok(()), - _ => { - match rest[0] as char { - // Parse a borrowed reference - '&' => { - let (ident, rest) = ident(rest); - traits.push(ident); - - attr_inner(rest, traits) - }, - // Parse a regular ident - 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 + 1 .. close], &mut traits)?, - _ => Err("attribute format should be `#[auto_impl(a, b, c)]`")? - } - - Ok(traits) -} diff --git a/src/proxy.rs b/src/proxy.rs new file mode 100644 index 0000000..ae48bb3 --- /dev/null +++ b/src/proxy.rs @@ -0,0 +1,158 @@ +use std::iter::Peekable; + +use proc_macro::{token_stream, TokenStream, TokenTree}; + +/// Types for which a trait can automatically be implemented. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ProxyType { + Ref, + RefMut, + Arc, + Rc, + Box, + Fn, + FnMut, + FnOnce, +} + +impl ProxyType { + pub(crate) fn is_fn(&self) -> bool { + match *self { + ProxyType::Fn | ProxyType::FnMut | ProxyType::FnOnce => true, + _ => false, + } + } +} + +/// Parses the attribute token stream into a list of proxy types. +/// +/// The attribute token stream is the one in `#[auto_impl(...)]`. It is +/// supposed to be a comma-separated list of possible proxy types. Legal values +/// are `&`, `&mut`, `Box`, `Rc`, `Arc`, `Fn`, `FnMut` and `FnOnce`. +/// +/// If the given TokenStream is not valid, errors are emitted as appropriate +/// and `Err(())` is returned. +pub(crate) fn parse_types(args: TokenStream) -> Result, ()> { + let mut out = Vec::new(); + let mut iter = args.into_iter().peekable(); + + // While there are still tokens left... + while iter.peek().is_some() { + // First, we expect one of the proxy types. + out.push(eat_type(&mut iter)?); + + // If the next token is a comma, we eat it (trailing commas are + // allowed). If not, nothing happens (in this case, it's probably the + // end of the stream, otherwise an error will occur later). + let comma_next = match iter.peek() { + Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => true, + _ => false, + }; + + if comma_next { + let _ = iter.next(); + } + } + + Ok(out) +} + +/// Parses one `ProxyType` from the given token iterator. The iterator must not +/// be empty! +fn eat_type(iter: &mut Peekable) -> Result { + const NOTE_TEXT: &str = "\ + attribute format should be `#[auto_impl()]` where `` is \ + a comma-separated list of types. Allowed values for types: `&`, \ + `&mut`, `Box`, `Rc`, `Arc`, `Fn`, `FnMut` and `FnOnce`.\ + "; + const EXPECTED_TEXT: &str = "Expected '&' or ident."; + + // We can unwrap because this function requires the iterator to be + // non-empty. + let ty = match iter.next().unwrap() { + TokenTree::Group(group) => { + group.span() + .error(format!("unexpected group. {}", EXPECTED_TEXT)) + .note(NOTE_TEXT) + .emit(); + + return Err(()); + } + + TokenTree::Literal(lit) => { + lit.span() + .error(format!("unexpected literal. {}", EXPECTED_TEXT)) + .note(NOTE_TEXT) + .emit(); + + return Err(()); + } + + TokenTree::Punct(punct) => { + // Only '&' are allowed. Everything else leads to an error. + if punct.as_char() != '&' { + let msg = format!("unexpected punctuation '{}'. {}", punct, EXPECTED_TEXT); + punct.span().error(msg).note(NOTE_TEXT).emit(); + + return Err(()); + } + + // Check if the next token is `mut`. If not, we will ignore it. + let is_mut_next = match iter.peek() { + Some(TokenTree::Ident(id)) if id.to_string() == "mut" => true, + _ => false, + }; + + if is_mut_next { + // Eat `mut` + let _ = iter.next(); + ProxyType::RefMut + } else { + ProxyType::Ref + } + } + + TokenTree::Ident(ident) => { + match &*ident.to_string() { + "Box" => ProxyType::Box, + "Rc" => ProxyType::Rc, + "Arc" => ProxyType::Arc, + "Fn" => ProxyType::Fn, + "FnMut" => ProxyType::FnMut, + "FnOnce" => ProxyType::FnOnce, + _ => { + let msg = format!("unexpected '{}'. {}", ident, EXPECTED_TEXT); + ident.span() + .error(msg) + .note(NOTE_TEXT) + .emit(); + + return Err(()); + } + } + } + }; + + Ok(ty) +} + + +#[cfg(test)] +mod test { + use proc_macro::TokenStream; + + use super::parse_types; + + #[test] + fn empty() { + assert_eq!( + parse_types(TokenStream::new()), + Ok(vec![]) + ); + } + + // Right now, we can't really write useful tests. Many functions from + // `proc_macro` use a compiler internal session. This session is only valid + // when we were actually called as a proc macro. We need to add tests once + // this limitation of `proc_macro` is fixed. +} diff --git a/src/spanned.rs b/src/spanned.rs new file mode 100644 index 0000000..2c433d6 --- /dev/null +++ b/src/spanned.rs @@ -0,0 +1,38 @@ +use proc_macro::{Span, TokenStream}; +use proc_macro2::TokenStream as TokenStream2; +use quote::ToTokens; + + +/// Helper trait to receive the span of a `syn` AST node. This is very similar +/// to `syn::Spanned`, but differs in that it results in a `proc_macro::Span` +/// and not a `proc_macro2::Span`. This is way better since we can directly +/// emit errors and warning from the former ones. +/// +/// This trait is implemented for all types that implement `ToTokens`. The +/// implementation is rather ugly, since we generate a complete token stream +/// of the node to get the span information of the underlying tokens. +pub trait Spanned { + /// Returns the span of this value. The value is expected not to span + /// multiple files -- else this method panics. + fn span(&self) -> Span; +} + +impl Spanned for T { + fn span(&self) -> Span { + // Convert the node into tokens + let mut tokens = TokenStream2::new(); + self.to_tokens(&mut tokens); + let tokens: TokenStream = tokens.into(); + + if tokens.is_empty() { + Span::call_site() + } else { + let mut iter = tokens.into_iter(); + let mut span = iter.next().unwrap().span(); + if let Some(last) = iter.last() { + span = span.join(last.span()).unwrap(); + } + span + } + } +}