From 64cf77f71b4d113460bb5f30430cbf33bd477974 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Tue, 3 Jul 2018 19:58:32 +0200 Subject: [PATCH 01/42] Remove almost everything --- Cargo.toml | 5 +- src/impl_as_ref.rs | 206 ----------------- src/impl_fn.rs | 139 ------------ src/lib.proc_macro.rs | 25 -- src/lib.rs | 517 +----------------------------------------- src/model.rs | 229 ------------------- src/parse.rs | 55 ----- 7 files changed, 5 insertions(+), 1171 deletions(-) delete mode 100644 src/impl_as_ref.rs delete mode 100644 src/impl_fn.rs delete mode 100644 src/lib.proc_macro.rs delete mode 100644 src/model.rs delete mode 100644 src/parse.rs diff --git a/Cargo.toml b/Cargo.toml index fa60aca..05dbba6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,5 +17,6 @@ travis-ci = { repository = "KodrAus/auto_impl" } proc-macro = true [dependencies] -quote = "~0.3" -syn = { version = "~0.11", features = ["full"] } +proc-macro2 = "0.4.6" +quote = "0.6.3" +syn = { version = "0.14.4", features = ["full"] } 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..d936f55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,520 +1,7 @@ -#![recursion_limit="128"] -#![cfg_attr(not(test), feature(proc_macro))] +#![feature(proc_macro)] -#[cfg(not(test))] -include!("lib.proc_macro.rs"); +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()) - } - }) - .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; - } - ); - - assert_invalid(&[ImplForTrait::Fn], input, "auto impl for `Fn` is only supported for traits with 1 method"); - } -} 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) -} From a63b0fe61cfbaf8ac79bb492d202e8d58bd6040e Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Tue, 3 Jul 2018 21:20:09 +0200 Subject: [PATCH 02/42] Add `proxy.rs` to parse proxy types --- src/lib.rs | 23 ++++++++- src/proxy.rs | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 src/proxy.rs diff --git a/src/lib.rs b/src/lib.rs index d936f55..f84deaa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,26 @@ #![feature(proc_macro)] -extern crate proc_macro2; -#[macro_use] +// extern crate proc_macro2; +extern crate proc_macro; +// #[macro_use] extern crate quote; extern crate syn; + + + +use proc_macro::{ + TokenStream, +}; + + +mod proxy; + + +#[proc_macro_attribute] +pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream { + let proxy_types = proxy::parse_types(args); + println!("{:?}", proxy_types); + + input +} diff --git a/src/proxy.rs b/src/proxy.rs new file mode 100644 index 0000000..9f9ff50 --- /dev/null +++ b/src/proxy.rs @@ -0,0 +1,137 @@ +use std::{ + iter::Peekable, +}; + +use proc_macro::{ + TokenStream, TokenTree, + token_stream, +}; + + +/// Types for which a trait can automatically be implemented. +#[derive(Debug)] +pub(crate) enum ProxyType { + Arc, + Rc, + Box, + Fn, + FnMut, + FnOnce, + Ref, + RefMut, +} + +/// 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) +} From 4825193969c56c3a84bf354c48bbe02d625373e0 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Tue, 3 Jul 2018 21:52:50 +0200 Subject: [PATCH 03/42] Add two simple tests for `proxy.rs` Some tests sadly still fail due to a bug/missing feature of proc-macro. --- src/proxy.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/proxy.rs b/src/proxy.rs index 9f9ff50..1a169ed 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -9,7 +9,7 @@ use proc_macro::{ /// Types for which a trait can automatically be implemented. -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum ProxyType { Arc, Rc, @@ -135,3 +135,31 @@ fn eat_type(iter: &mut Peekable) -> Result Date: Tue, 3 Jul 2018 22:29:11 +0200 Subject: [PATCH 04/42] Add basic structure --- Cargo.toml | 3 ++- src/lib.rs | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 05dbba6..d2b66bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,5 @@ proc-macro = true [dependencies] proc-macro2 = "0.4.6" quote = "0.6.3" -syn = { version = "0.14.4", features = ["full"] } +# TODO: remove "extra-traits", it's just for debugging +syn = { version = "0.14.4", features = ["full", "extra-traits"] } diff --git a/src/lib.rs b/src/lib.rs index f84deaa..c2c0c54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ extern crate syn; use proc_macro::{ - TokenStream, + TokenStream, Diagnostic, Level, Span, }; @@ -19,8 +19,35 @@ mod proxy; #[proc_macro_attribute] pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream { - let proxy_types = proxy::parse_types(args); - println!("{:?}", proxy_types); - - input + // 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)?; + println!("Proxy types: {:?}", proxy_types); + + // Try to parse the + match syn::parse::(input.clone()) { + Ok(trait_def) => { + println!("{:#?}", trait_def); + + // TODO: generate impls + Ok(TokenStream::new()) + } + 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(()) + } + } + }().unwrap_or(TokenStream::new()); + + // 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() } From a85d6577fc313ad70ff104dfcf769a903026dddd Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Tue, 3 Jul 2018 22:29:41 +0200 Subject: [PATCH 05/42] Add simple example to test with --- examples/simple.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 examples/simple.rs diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..ce7152a --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,15 @@ +#![feature(proc_macro)] + +extern crate auto_impl; + +use auto_impl::auto_impl; + + +#[auto_impl(&, Box, &mut)] +trait Foo { + fn foo(&self); +} + + + +fn main() {} From 056e596e830a00ac6c1a199c93148378494e01a3 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 09:24:08 +0200 Subject: [PATCH 06/42] Add simple impl generator (still uncomplete) It ignores generics of the trait and doesn't generate code for trait items yet. --- examples/simple.rs | 2 +- src/gen.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 11 +++++--- src/proxy.rs | 4 +-- 4 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 src/gen.rs diff --git a/examples/simple.rs b/examples/simple.rs index ce7152a..4ef4e8e 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -7,7 +7,7 @@ use auto_impl::auto_impl; #[auto_impl(&, Box, &mut)] trait Foo { - fn foo(&self); + // fn foo(&self); } diff --git a/src/gen.rs b/src/gen.rs new file mode 100644 index 0000000..abee1f8 --- /dev/null +++ b/src/gen.rs @@ -0,0 +1,65 @@ +use proc_macro::{ + TokenStream, +}; +use proc_macro2::TokenStream as TokenStream2; + + +use crate::proxy::ProxyType; + + +pub(crate) fn gen_impls( + proxy_types: &[ProxyType], + trait_def: &syn::ItemTrait, +) -> Result { + // One impl for each proxy type + let tokens = proxy_types.into_iter().map(|proxy_type| { + let header = header(proxy_type, trait_def); + + let out = quote! { + #header { + // TODO + } + }; + + TokenStream::from(out) + }).collect(); + + Ok(tokens) +} + +fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { + let trait_name = &trait_def.ident; + + // The tokens after `for` in the impl header + let self_ty = match *proxy_type { + ProxyType::Ref => quote! { &'a T }, + ProxyType::RefMut => quote! { &'a mut T }, + ProxyType::Arc => quote! { ::std::sync::Arc }, + ProxyType::Rc => quote! { ::std::rc::Rc }, + ProxyType::Box => quote! { ::std::boxed::Box }, + ProxyType::Fn => unimplemented!(), + ProxyType::FnMut => unimplemented!(), + ProxyType::FnOnce => unimplemented!(), + }; + + let where_bounds = TokenStream2::new(); + + let impl_params = { + match *proxy_type { + ProxyType::Ref => quote! { 'a, T: 'a }, + ProxyType::RefMut => quote! { 'a, T: 'a }, + ProxyType::Arc => quote! { T }, + ProxyType::Rc => quote! { T }, + ProxyType::Box => quote! { T }, + ProxyType::Fn => unimplemented!(), + ProxyType::FnMut => unimplemented!(), + ProxyType::FnOnce => unimplemented!(), + } + }; + + quote! { + impl< #impl_params > #trait_name for #self_ty + where + #where_bounds + } +} diff --git a/src/lib.rs b/src/lib.rs index c2c0c54..5b825b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,11 @@ +#![feature(crate_in_paths)] +#![feature(extern_prelude)] #![feature(proc_macro)] -// extern crate proc_macro2; extern crate proc_macro; -// #[macro_use] +extern crate proc_macro2; +#[macro_use] extern crate quote; extern crate syn; @@ -14,6 +16,7 @@ use proc_macro::{ }; +mod gen; mod proxy; @@ -32,8 +35,8 @@ pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream { Ok(trait_def) => { println!("{:#?}", trait_def); - // TODO: generate impls - Ok(TokenStream::new()) + let impls = gen::gen_impls(&proxy_types, &trait_def)?; + Ok(impls) } Err(e) => { let msg = "couldn't parse trait item"; diff --git a/src/proxy.rs b/src/proxy.rs index 1a169ed..e31d2e4 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -11,14 +11,14 @@ use proc_macro::{ /// 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, - Ref, - RefMut, } /// Parses the attribute token stream into a list of proxy types. From 8dab34c113158ad1442896501e3e61db29f12be7 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 14:31:24 +0200 Subject: [PATCH 07/42] Add first code generating the header of the trait impl --- Cargo.toml | 2 +- examples/simple.rs | 2 +- src/gen.rs | 101 ++++++++++++++++++++++++++++++++++++--------- src/lib.rs | 12 ++++-- src/proxy.rs | 9 ++++ 5 files changed, 101 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d2b66bb..db4bd7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ travis-ci = { repository = "KodrAus/auto_impl" } proc-macro = true [dependencies] -proc-macro2 = "0.4.6" +proc-macro2 = { version = "0.4.6", features = ["nightly"] } quote = "0.6.3" # TODO: remove "extra-traits", it's just for debugging syn = { version = "0.14.4", features = ["full", "extra-traits"] } diff --git a/examples/simple.rs b/examples/simple.rs index 4ef4e8e..16daaeb 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -6,7 +6,7 @@ use auto_impl::auto_impl; #[auto_impl(&, Box, &mut)] -trait Foo { +trait Foo<'a, X, Y = i32> { // fn foo(&self); } diff --git a/src/gen.rs b/src/gen.rs index abee1f8..3ade8eb 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -1,12 +1,24 @@ use proc_macro::{ TokenStream, }; -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{ + TokenStream as TokenStream2, +}; +use quote::{TokenStreamExt}; +use syn::{GenericParam, TypeParam, LifetimeDef}; use crate::proxy::ProxyType; +fn proxy_ty_param_ident() -> TokenStream2 { + quote! { __ProxyT } +} + +fn proxy_lifetime_ident() -> TokenStream2 { + quote! { '__proxy_lifetime } +} + pub(crate) fn gen_impls( proxy_types: &[ProxyType], trait_def: &syn::ItemTrait, @@ -28,37 +40,86 @@ pub(crate) fn gen_impls( } fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { - let trait_name = &trait_def.ident; + let proxy_ty_param = proxy_ty_param_ident(); // The tokens after `for` in the impl header let self_ty = match *proxy_type { - ProxyType::Ref => quote! { &'a T }, - ProxyType::RefMut => quote! { &'a mut T }, - ProxyType::Arc => quote! { ::std::sync::Arc }, - ProxyType::Rc => quote! { ::std::rc::Rc }, - ProxyType::Box => quote! { ::std::boxed::Box }, - ProxyType::Fn => unimplemented!(), - ProxyType::FnMut => unimplemented!(), - ProxyType::FnOnce => unimplemented!(), + ProxyType::Ref => quote! { &'a #proxy_ty_param }, + ProxyType::RefMut => quote! { &'a 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 => proxy_ty_param.clone(), + ProxyType::FnMut => proxy_ty_param.clone(), + ProxyType::FnOnce => proxy_ty_param.clone(), + }; + + let trait_params = { + let mut params = TokenStream2::new(); + for trait_param in &trait_def.generics.params { + match trait_param { + GenericParam::Type(TypeParam { ident, ..}) => { + params.append_all(quote! { #ident , }); + } + GenericParam::Lifetime(LifetimeDef { lifetime, ..}) => { + params.append_all(quote! { #lifetime , }); + } + GenericParam::Const(_) => { + unimplemented!() + } + } + } + + params }; let where_bounds = TokenStream2::new(); + + // 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 parameter added. For a trait `trait Foo<'x, 'y, A, B>`, + // it will look like this: + // + // '__ProxyLifetime, 'x, 'y, A, B, __ProxyT + // + // The `'__ProxyLifetime` in the beginning is only added when the proxy + // type is `&` or `&mut`. let impl_params = { - match *proxy_type { - ProxyType::Ref => quote! { 'a, T: 'a }, - ProxyType::RefMut => quote! { 'a, T: 'a }, - ProxyType::Arc => quote! { T }, - ProxyType::Rc => quote! { T }, - ProxyType::Box => quote! { T }, - ProxyType::Fn => unimplemented!(), - ProxyType::FnMut => unimplemented!(), - ProxyType::FnOnce => unimplemented!(), + // Determine if our proxy type needs a lifetime parameter + let mut params = if proxy_type.is_ref() { + let lifetime_ident = proxy_lifetime_ident(); + quote! { #lifetime_ident, } + } else { + quote! {} + }; + + // Append all parameters from the trait + for trait_param in &trait_def.generics.params { + match trait_param { + GenericParam::Type(TypeParam { ident, bounds, ..}) => { + params.append_all(quote! { #ident : #bounds , }); + } + GenericParam::Lifetime(lifetime) => { + // TODO: think about attributes? + params.append_all(quote! { #lifetime , }); + } + GenericParam::Const(_) => { + unimplemented!() + } + } } + + // Append proxy type parameter + params.append_all(proxy_ty_param); + + params }; + let trait_name = &trait_def.ident; + quote! { - impl< #impl_params > #trait_name for #self_ty + impl<#impl_params> #trait_name<#trait_params> for #self_ty where #where_bounds } diff --git a/src/lib.rs b/src/lib.rs index 5b825b1..1c27655 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,14 +33,14 @@ pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream { // Try to parse the match syn::parse::(input.clone()) { Ok(trait_def) => { - println!("{:#?}", trait_def); + // println!("{:#?}", trait_def); let impls = gen::gen_impls(&proxy_types, &trait_def)?; Ok(impls) } Err(e) => { let msg = "couldn't parse trait item"; - Diagnostic::spanned(Span::call_site(), Level::Error , msg) + Diagnostic::spanned(Span::call_site(), Level::Error, msg) .note(e.to_string()) .note("the #[auto_impl] attribute can only be applied to traits!") .emit(); @@ -52,5 +52,11 @@ pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream { // 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() + let out = vec![input, impls].into_iter().collect(); + + // println!("--------------------"); + // println!("{}", out); + // println!("--------------------"); + + out } diff --git a/src/proxy.rs b/src/proxy.rs index e31d2e4..ea0bee1 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -21,6 +21,15 @@ pub(crate) enum ProxyType { FnOnce, } +impl ProxyType { + pub(crate) fn is_ref(&self) -> bool { + match *self { + ProxyType::Ref | ProxyType::RefMut => 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 From b8e2796852afeff660163c5e2ce19e178ef6915f Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 16:00:16 +0200 Subject: [PATCH 08/42] Add a second, unused way to generate the impl header This is just for testing to see what approach will result in better and shorter code. --- examples/simple.rs | 2 +- src/gen.rs | 253 ++++++++++++++++++++++++++++++++------------- src/lib.rs | 7 +- 3 files changed, 188 insertions(+), 74 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 16daaeb..33aaaf2 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,7 +5,7 @@ extern crate auto_impl; use auto_impl::auto_impl; -#[auto_impl(&, Box, &mut)] +#[auto_impl(&, &mut)] trait Foo<'a, X, Y = i32> { // fn foo(&self); } diff --git a/src/gen.rs b/src/gen.rs index 3ade8eb..adeccaf 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -1,46 +1,177 @@ -use proc_macro::{ - TokenStream, -}; use proc_macro2::{ TokenStream as TokenStream2, + Span, +}; +use quote::{TokenStreamExt, ToTokens}; +use syn::{ + GenericParam, TypeParam, LifetimeDef, ItemImpl, Path, PathSegment, PathArguments, Type, + TypeReference, TypePath, Ident, Lifetime, Generics, TypeParamBound, + punctuated::Punctuated, + token, }; -use quote::{TokenStreamExt}; -use syn::{GenericParam, TypeParam, LifetimeDef}; use crate::proxy::ProxyType; +const PROXY_TY_PARAM_NAME: &str = "__AutoImplProxyT"; +const PROXY_LT_PARAM_NAME: &str = "'__auto_impl_proxy_lifetime"; -fn proxy_ty_param_ident() -> TokenStream2 { - quote! { __ProxyT } -} +// fn proxy_ty_param_ident() -> TokenStream2 { +// quote! { __ProxyT } +// } -fn proxy_lifetime_ident() -> TokenStream2 { - quote! { '__proxy_lifetime } -} +// fn proxy_lifetime_ident() -> TokenStream2 { +// quote! { '__proxy_lifetime } +// } pub(crate) fn gen_impls( proxy_types: &[ProxyType], trait_def: &syn::ItemTrait, -) -> Result { +) -> Result<::proc_macro::TokenStream, ()> { + let mut tokens = TokenStream2::new(); + // One impl for each proxy type - let tokens = proxy_types.into_iter().map(|proxy_type| { + for proxy_type in proxy_types { let header = header(proxy_type, trait_def); - let out = quote! { + tokens.append_all(quote! { #header { // TODO } - }; - - TokenStream::from(out) - }).collect(); + }); + } - Ok(tokens) + Ok(tokens.into()) } +// 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 { +// impl_def(proxy_type, trait_def)?.to_tokens(&mut tokens); +// } + +// Ok(tokens.into()) +// } + +// fn impl_def(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> Result { +// let (impl_generics, ty_generics, _where_clause) = trait_def.generics.split_for_impl(); + +// let trait_ = { +// let mut segments = Punctuated::new(); +// segments.push(PathSegment { +// ident: trait_def.ident.clone(), +// arguments: PathArguments::AngleBracketed(, // TODO +// }); + +// let path = Path { +// leading_colon: None, +// segments, +// }; + +// // No bang, trait name, simple `for` +// (None, path, Token![for](Span::call_site())) +// }; + +// let impl_generics = { +// // The only way to get normal `Generics` from `ImplGenerics` is to +// // first convert it into a token stream and then parse it. +// let mut g = syn::parse2::(impl_generics.into_token_stream()).unwrap(); + +// let lifetime = Lifetime::new(PROXY_LT_PARAM_NAME, Span::call_site()); + +// if proxy_type.is_ref() { +// let lifetime_def = LifetimeDef { +// attrs: vec![], +// lifetime: lifetime.clone(), +// colon_token: None, +// bounds: Punctuated::new(), +// }; +// g.params.insert(0, GenericParam::Lifetime(lifetime_def)); +// } + +// let bounds = if proxy_type.is_ref() { +// let mut b = Punctuated::new(); +// b.push(TypeParamBound::Lifetime(lifetime)); +// b +// } else { +// Punctuated::new() +// }; + +// g.params.push(GenericParam::Type(TypeParam { +// attrs: vec![], +// ident: Ident::new(PROXY_TY_PARAM_NAME, Span::call_site()), +// colon_token: Some(Token![:]([Span::call_site()])), +// bounds, +// eq_token: None, +// default: None, +// })); + +// g +// }; + +// let item = ItemImpl { +// attrs: vec![], +// defaultness: None, +// unsafety: None, +// impl_token: Token![impl](Span::call_site()), +// generics: impl_generics, //Generics, +// trait_: Some(trait_), +// self_ty: Box::new(self_type(proxy_type)), +// brace_token: token::Brace(Span::call_site()), +// items: vec![], +// }; + +// Ok(item) +// } + +// fn self_type(proxy_type: &ProxyType) -> Type { +// let type_t = { +// let mut segments = Punctuated::new(); +// segments.push(PathSegment { +// ident: Ident::new(PROXY_TY_PARAM_NAME, Span::call_site()), +// arguments: PathArguments::None, // TODO +// }); + +// Type::Path(TypePath { +// qself: None, +// path: Path { +// leading_colon: None, +// segments, +// }, +// }) +// }; + + +// match *proxy_type { +// ProxyType::Ref => Type::Reference(TypeReference { +// and_token: Token!(&)([Span::call_site()]), +// lifetime: Some(Lifetime::new(PROXY_LT_PARAM_NAME, Span::call_site())), +// mutability: None, +// elem: Box::new(type_t), +// }), +// ProxyType::RefMut => Type::Reference(TypeReference { +// and_token: Token![&]([Span::call_site()]), +// lifetime: Some(Lifetime::new(PROXY_LT_PARAM_NAME, Span::call_site())), +// mutability: Some(Token![mut](Span::call_site())), +// elem: Box::new(type_t), +// }), +// // ProxyType::Arc => , +// // ProxyType::Rc => , +// // ProxyType::Box => , +// ProxyType::Fn => type_t, +// ProxyType::FnMut => type_t, +// ProxyType::FnOnce => type_t, +// _ => unimplemented!(), +// } +// } + fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { - let proxy_ty_param = proxy_ty_param_ident(); + let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span::call_site()); // proxy_ty_param_ident(); // The tokens after `for` in the impl header let self_ty = match *proxy_type { @@ -49,69 +180,51 @@ fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { 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 => proxy_ty_param.clone(), - ProxyType::FnMut => proxy_ty_param.clone(), - ProxyType::FnOnce => proxy_ty_param.clone(), + ProxyType::Fn => quote! { proxy_ty_param }, + ProxyType::FnMut => quote! { proxy_ty_param }, + ProxyType::FnOnce => quote! { proxy_ty_param }, }; - let trait_params = { - let mut params = TokenStream2::new(); - for trait_param in &trait_def.generics.params { - match trait_param { - GenericParam::Type(TypeParam { ident, ..}) => { - params.append_all(quote! { #ident , }); - } - GenericParam::Lifetime(LifetimeDef { lifetime, ..}) => { - params.append_all(quote! { #lifetime , }); - } - GenericParam::Const(_) => { - unimplemented!() - } - } - } - - params - }; - - let where_bounds = TokenStream2::new(); + let (impl_generics, trait_generics, where_clause) = trait_def.generics.split_for_impl(); // 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 parameter added. For a trait `trait Foo<'x, 'y, A, B>`, + // one or two parameters added. For a trait `trait Foo<'x, 'y, A, B>`, // it will look like this: // - // '__ProxyLifetime, 'x, 'y, A, B, __ProxyT + // '__auto_impl_proxy_lifetime, 'x, 'y, A, B, __AutoImplProxyT // - // The `'__ProxyLifetime` in the beginning is only added when the proxy - // type is `&` or `&mut`. + // The `'__auto_impl_proxy_lifetime` in the beginning is only added when + // the proxy type is `&` or `&mut`. let impl_params = { // Determine if our proxy type needs a lifetime parameter - let mut params = if proxy_type.is_ref() { - let lifetime_ident = proxy_lifetime_ident(); - quote! { #lifetime_ident, } - } else { - quote! {} + let (mut params, ty_bounds) = match proxy_type { + ProxyType::Ref | ProxyType::RefMut => { + let lifetime = &Lifetime::new(PROXY_LT_PARAM_NAME, Span::call_site()); + (quote! { #lifetime, }, quote! { : #lifetime }) + } + ProxyType::Box | ProxyType::Rc | ProxyType::Arc => { + (quote! {}, quote! {}) + } + ProxyType::Fn => (quote! {}, quote! { : ::std::ops::Fn }), + ProxyType::FnMut => (quote! {}, quote! { : ::std::ops::FnMut }), + ProxyType::FnOnce => (quote! {}, quote! { : ::std::ops::FnOnce }), }; - // Append all parameters from the trait - for trait_param in &trait_def.generics.params { - match trait_param { - GenericParam::Type(TypeParam { ident, bounds, ..}) => { - params.append_all(quote! { #ident : #bounds , }); - } - GenericParam::Lifetime(lifetime) => { - // TODO: think about attributes? - params.append_all(quote! { #lifetime , }); - } - GenericParam::Const(_) => { - unimplemented!() - } - } - } + // 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 - params.append_all(proxy_ty_param); + params.append_all(quote! { , #proxy_ty_param #ty_bounds }); params }; @@ -119,8 +232,8 @@ fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { let trait_name = &trait_def.ident; quote! { - impl<#impl_params> #trait_name<#trait_params> for #self_ty + impl<#impl_params> #trait_name #trait_generics for #self_ty where - #where_bounds + #where_clause } } diff --git a/src/lib.rs b/src/lib.rs index 1c27655..edfc5e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ extern crate proc_macro; extern crate proc_macro2; #[macro_use] extern crate quote; +#[macro_use] extern crate syn; @@ -54,9 +55,9 @@ pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream { // generated impls (or nothing if an error occured). let out = vec![input, impls].into_iter().collect(); - // println!("--------------------"); - // println!("{}", out); - // println!("--------------------"); + println!("--------------------"); + println!("{}", out); + println!("--------------------"); out } From 96fd6828bdc6ba05730bc64f53fcebb9e1a4d533 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 16:09:09 +0200 Subject: [PATCH 09/42] Remove second way of generating the head and shorten old code --- src/gen.rs | 182 ++++++++--------------------------------------------- src/lib.rs | 1 - 2 files changed, 26 insertions(+), 157 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index adeccaf..0fe5869 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -4,10 +4,7 @@ use proc_macro2::{ }; use quote::{TokenStreamExt, ToTokens}; use syn::{ - GenericParam, TypeParam, LifetimeDef, ItemImpl, Path, PathSegment, PathArguments, Type, - TypeReference, TypePath, Ident, Lifetime, Generics, TypeParamBound, - punctuated::Punctuated, - token, + Ident, Lifetime, }; @@ -16,13 +13,6 @@ use crate::proxy::ProxyType; const PROXY_TY_PARAM_NAME: &str = "__AutoImplProxyT"; const PROXY_LT_PARAM_NAME: &str = "'__auto_impl_proxy_lifetime"; -// fn proxy_ty_param_ident() -> TokenStream2 { -// quote! { __ProxyT } -// } - -// fn proxy_lifetime_ident() -> TokenStream2 { -// quote! { '__proxy_lifetime } -// } pub(crate) fn gen_impls( proxy_types: &[ProxyType], @@ -44,149 +34,16 @@ pub(crate) fn gen_impls( Ok(tokens.into()) } -// 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 { -// impl_def(proxy_type, trait_def)?.to_tokens(&mut tokens); -// } - -// Ok(tokens.into()) -// } - -// fn impl_def(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> Result { -// let (impl_generics, ty_generics, _where_clause) = trait_def.generics.split_for_impl(); - -// let trait_ = { -// let mut segments = Punctuated::new(); -// segments.push(PathSegment { -// ident: trait_def.ident.clone(), -// arguments: PathArguments::AngleBracketed(, // TODO -// }); - -// let path = Path { -// leading_colon: None, -// segments, -// }; - -// // No bang, trait name, simple `for` -// (None, path, Token![for](Span::call_site())) -// }; - -// let impl_generics = { -// // The only way to get normal `Generics` from `ImplGenerics` is to -// // first convert it into a token stream and then parse it. -// let mut g = syn::parse2::(impl_generics.into_token_stream()).unwrap(); - -// let lifetime = Lifetime::new(PROXY_LT_PARAM_NAME, Span::call_site()); - -// if proxy_type.is_ref() { -// let lifetime_def = LifetimeDef { -// attrs: vec![], -// lifetime: lifetime.clone(), -// colon_token: None, -// bounds: Punctuated::new(), -// }; -// g.params.insert(0, GenericParam::Lifetime(lifetime_def)); -// } - -// let bounds = if proxy_type.is_ref() { -// let mut b = Punctuated::new(); -// b.push(TypeParamBound::Lifetime(lifetime)); -// b -// } else { -// Punctuated::new() -// }; - -// g.params.push(GenericParam::Type(TypeParam { -// attrs: vec![], -// ident: Ident::new(PROXY_TY_PARAM_NAME, Span::call_site()), -// colon_token: Some(Token![:]([Span::call_site()])), -// bounds, -// eq_token: None, -// default: None, -// })); - -// g -// }; - -// let item = ItemImpl { -// attrs: vec![], -// defaultness: None, -// unsafety: None, -// impl_token: Token![impl](Span::call_site()), -// generics: impl_generics, //Generics, -// trait_: Some(trait_), -// self_ty: Box::new(self_type(proxy_type)), -// brace_token: token::Brace(Span::call_site()), -// items: vec![], -// }; - -// Ok(item) -// } - -// fn self_type(proxy_type: &ProxyType) -> Type { -// let type_t = { -// let mut segments = Punctuated::new(); -// segments.push(PathSegment { -// ident: Ident::new(PROXY_TY_PARAM_NAME, Span::call_site()), -// arguments: PathArguments::None, // TODO -// }); - -// Type::Path(TypePath { -// qself: None, -// path: Path { -// leading_colon: None, -// segments, -// }, -// }) -// }; - - -// match *proxy_type { -// ProxyType::Ref => Type::Reference(TypeReference { -// and_token: Token!(&)([Span::call_site()]), -// lifetime: Some(Lifetime::new(PROXY_LT_PARAM_NAME, Span::call_site())), -// mutability: None, -// elem: Box::new(type_t), -// }), -// ProxyType::RefMut => Type::Reference(TypeReference { -// and_token: Token![&]([Span::call_site()]), -// lifetime: Some(Lifetime::new(PROXY_LT_PARAM_NAME, Span::call_site())), -// mutability: Some(Token![mut](Span::call_site())), -// elem: Box::new(type_t), -// }), -// // ProxyType::Arc => , -// // ProxyType::Rc => , -// // ProxyType::Box => , -// ProxyType::Fn => type_t, -// ProxyType::FnMut => type_t, -// ProxyType::FnOnce => type_t, -// _ => unimplemented!(), -// } -// } - fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { - let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span::call_site()); // proxy_ty_param_ident(); - - // The tokens after `for` in the impl header - let self_ty = match *proxy_type { - ProxyType::Ref => quote! { &'a #proxy_ty_param }, - ProxyType::RefMut => quote! { &'a 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 }, - }; + let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span::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 @@ -197,15 +54,15 @@ fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { // // The `'__auto_impl_proxy_lifetime` in the beginning is only added when // the proxy type is `&` or `&mut`. - let impl_params = { + let impl_generics = { // Determine if our proxy type needs a lifetime parameter let (mut params, ty_bounds) = match proxy_type { ProxyType::Ref | ProxyType::RefMut => { let lifetime = &Lifetime::new(PROXY_LT_PARAM_NAME, Span::call_site()); - (quote! { #lifetime, }, quote! { : #lifetime }) + (quote! { #lifetime, }, quote! { : #lifetime + #trait_path }) } ProxyType::Box | ProxyType::Rc | ProxyType::Arc => { - (quote! {}, quote! {}) + (quote! {}, quote! { : #trait_path }) } ProxyType::Fn => (quote! {}, quote! { : ::std::ops::Fn }), ProxyType::FnMut => (quote! {}, quote! { : ::std::ops::FnMut }), @@ -220,7 +77,6 @@ fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { .skip(1) // the opening `<` .collect::>(); tts.pop(); // the closing `>` - params.append_all(tts); // Append proxy type parameter @@ -229,10 +85,24 @@ fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { params }; - let trait_name = &trait_def.ident; + // The tokens after `for` in the impl header (the type the trait is + // implemented for). + let self_ty = match *proxy_type { + ProxyType::Ref => quote! { &'a #proxy_ty_param }, + ProxyType::RefMut => quote! { &'a 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 quote! { - impl<#impl_params> #trait_name #trait_generics for #self_ty + impl<#impl_generics> #trait_path for #self_ty where #where_clause } diff --git a/src/lib.rs b/src/lib.rs index edfc5e3..b867728 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ extern crate proc_macro; extern crate proc_macro2; #[macro_use] extern crate quote; -#[macro_use] extern crate syn; From 2f21d625271c77a32394bb186ff44a6ee13fa947 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 16:19:51 +0200 Subject: [PATCH 10/42] Fix three bugs in the header generation code --- src/gen.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index 0fe5869..4fc3e8a 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -64,9 +64,7 @@ fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { ProxyType::Box | ProxyType::Rc | ProxyType::Arc => { (quote! {}, quote! { : #trait_path }) } - ProxyType::Fn => (quote! {}, quote! { : ::std::ops::Fn }), - ProxyType::FnMut => (quote! {}, quote! { : ::std::ops::FnMut }), - ProxyType::FnOnce => (quote! {}, quote! { : ::std::ops::FnOnce }), + _ => unimplemented!(), }; // Append all parameters from the trait. Sadly, `impl_generics` @@ -79,8 +77,14 @@ fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { tts.pop(); // the closing `>` params.append_all(tts); - // Append proxy type parameter - params.append_all(quote! { , #proxy_ty_param #ty_bounds }); + // 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() { + quote! {} + } else { + quote! { , } + }; + params.append_all(quote! { #comma #proxy_ty_param #ty_bounds }); params }; @@ -94,16 +98,14 @@ fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { 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 }, + ProxyType::Fn => quote! { #proxy_ty_param }, + ProxyType::FnMut => quote! { #proxy_ty_param }, + ProxyType::FnOnce => quote! { #proxy_ty_param }, }; // Combine everything quote! { - impl<#impl_generics> #trait_path for #self_ty - where - #where_clause + impl<#impl_generics> #trait_path for #self_ty #where_clause } } From 96d255870b9486294ed241404d507e0670a014c7 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 16:58:57 +0200 Subject: [PATCH 11/42] Disable unused code for now to fix warning --- src/proxy.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/proxy.rs b/src/proxy.rs index ea0bee1..c145d7a 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -21,14 +21,15 @@ pub(crate) enum ProxyType { FnOnce, } -impl ProxyType { - pub(crate) fn is_ref(&self) -> bool { - match *self { - ProxyType::Ref | ProxyType::RefMut => true, - _ => false, - } - } -} +// TODO +// impl ProxyType { +// pub(crate) fn is_ref(&self) -> bool { +// match *self { +// ProxyType::Ref | ProxyType::RefMut => true, +// _ => false, +// } +// } +// } /// Parses the attribute token stream into a list of proxy types. /// From d0acac7d27abb04c6817912da90dff0e713b38e5 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 16:59:45 +0200 Subject: [PATCH 12/42] Add basics for generating trait items --- src/gen.rs | 28 +++++++++++++++++++++++----- src/lib.rs | 3 ++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index 4fc3e8a..87b6381 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -4,7 +4,7 @@ use proc_macro2::{ }; use quote::{TokenStreamExt, ToTokens}; use syn::{ - Ident, Lifetime, + Ident, Lifetime, ItemTrait, TraitItem, TraitItemMethod }; @@ -23,18 +23,17 @@ pub(crate) fn gen_impls( // One impl for each proxy type for proxy_type in proxy_types { let header = header(proxy_type, trait_def); + let items = items(proxy_type, trait_def)?; tokens.append_all(quote! { - #header { - // TODO - } + #header { #( #items )* } }); } Ok(tokens.into()) } -fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { +fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> TokenStream2 { let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span::call_site()); // Generate generics for impl positions from trait generics. @@ -109,3 +108,22 @@ fn header(proxy_type: &ProxyType, trait_def: &syn::ItemTrait) -> TokenStream2 { impl<#impl_generics> #trait_path for #self_ty #where_clause } } + +fn items( + proxy_type: &ProxyType, + trait_def: &ItemTrait, +) -> Result, ()> { + trait_def.items.iter().map(|item| { + match item { + TraitItem::Const(_) => unimplemented!(), + TraitItem::Method(method) => method_item(proxy_type, method), + TraitItem::Type(_) => unimplemented!(), + TraitItem::Macro(_) => unimplemented!(), + TraitItem::Verbatim(_) => unimplemented!(), + } + }).collect() +} + +fn method_item(_proxy_type: &ProxyType, _item: &TraitItemMethod) -> Result { + Ok(TokenStream2::new()) +} diff --git a/src/lib.rs b/src/lib.rs index b867728..ec6bebb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![feature(crate_in_paths)] #![feature(extern_prelude)] +#![feature(in_band_lifetimes)] #![feature(proc_macro)] @@ -28,7 +29,7 @@ pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream { // Try to parse the token stream from the attribute to get a list of // proxy types. let proxy_types = proxy::parse_types(args)?; - println!("Proxy types: {:?}", proxy_types); + // println!("Proxy types: {:?}", proxy_types); // Try to parse the match syn::parse::(input.clone()) { From f05169ac22f054b47d21ac344c75e24fdd7413eb Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 17:00:17 +0200 Subject: [PATCH 13/42] Add another example to `simple` --- examples/simple.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/examples/simple.rs b/examples/simple.rs index 33aaaf2..6b4bc93 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -10,6 +10,19 @@ trait Foo<'a, X, Y = i32> { // fn foo(&self); } +#[auto_impl(Box)] +trait MyTrait<'a, T> + where T: AsRef +{ + // type Type1; + // type Type2; + + // fn execute1<'b>(&'a self, arg1: &'b T) -> Result; + // fn execute2(&self) -> Self::Type2; + // fn execute3(self) -> Self::Type1; + // fn execute4() -> &'static str; +} + fn main() {} From 20639d1267a15d98f5917e4580affe3d501db9fb Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 18:06:25 +0200 Subject: [PATCH 14/42] Import proc_macro2::Span as Span2 --- src/gen.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index 87b6381..2a33ee5 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -1,6 +1,6 @@ use proc_macro2::{ TokenStream as TokenStream2, - Span, + Span as Span2, }; use quote::{TokenStreamExt, ToTokens}; use syn::{ @@ -34,7 +34,7 @@ pub(crate) fn gen_impls( } fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> TokenStream2 { - let proxy_ty_param = Ident::new(PROXY_TY_PARAM_NAME, Span::call_site()); + let proxy_ty_param = Ident::new(PROXY_TY_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(); @@ -57,7 +57,7 @@ fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> TokenStream2 { // Determine if our proxy type needs a lifetime parameter let (mut params, ty_bounds) = match proxy_type { ProxyType::Ref | ProxyType::RefMut => { - let lifetime = &Lifetime::new(PROXY_LT_PARAM_NAME, Span::call_site()); + let lifetime = &Lifetime::new(PROXY_LT_PARAM_NAME, Span2::call_site()); (quote! { #lifetime, }, quote! { : #lifetime + #trait_path }) } ProxyType::Box | ProxyType::Rc | ProxyType::Arc => { From fbc13da442d633aceeab995411bb476a3437c984 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 18:07:43 +0200 Subject: [PATCH 15/42] Add first version of method_item --- src/gen.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index 2a33ee5..6cfef0f 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -4,7 +4,7 @@ use proc_macro2::{ }; use quote::{TokenStreamExt, ToTokens}; use syn::{ - Ident, Lifetime, ItemTrait, TraitItem, TraitItemMethod + Ident, Lifetime, ItemTrait, TraitItem, TraitItemMethod, FnArg, }; @@ -124,6 +124,61 @@ fn items( }).collect() } -fn method_item(_proxy_type: &ProxyType, _item: &TraitItemMethod) -> Result { - Ok(TokenStream2::new()) +fn method_item(proxy_type: &ProxyType, item: &TraitItemMethod) -> Result { + enum SelfType { + None, + Ref, + Mut, + Value, + } + + let sig = &item.sig; + let name = &sig.ident; + let args = TokenStream2::new(); // TODO + + let self_arg = 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, + }; + + // Check if this method can be implemented for the given proxy type + match (proxy_type, self_arg) { + (ProxyType::Ref, SelfType::Mut) | + (ProxyType::Ref, SelfType::Value) => { + + } + (ProxyType::RefMut, SelfType::Value) => { + + } + (ProxyType::Rc, SelfType::Mut) | + (ProxyType::Rc, SelfType::Value) | + (ProxyType::Arc, SelfType::Mut) | + (ProxyType::Arc, SelfType::Value) => { + + } + (ProxyType::Fn, _) | + (ProxyType::FnMut, _) | + (ProxyType::FnOnce, _) => { + unimplemented!() + } + _ => {} // All other combinations are fine + } + + let body = match proxy_type { + ProxyType::Ref | ProxyType::RefMut | ProxyType::Arc | ProxyType::Rc | ProxyType::Box => { + quote! { + (**self).#name(#args) + } + } + ProxyType::Fn | ProxyType::FnMut | ProxyType::FnOnce => { + quote! { + self(#args) + } + } + }; + + + Ok(quote! { #sig { #body }}) } From bae0c2cd39b7e06718127167b201e6b2c6ff1944 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 18:39:06 +0200 Subject: [PATCH 16/42] Add `spanned` module to obtain useful Span information --- src/lib.rs | 1 + src/spanned.rs | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/spanned.rs diff --git a/src/lib.rs b/src/lib.rs index ec6bebb..db71406 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ use proc_macro::{ mod gen; mod proxy; +mod spanned; #[proc_macro_attribute] 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 + } + } +} From a17b02c99e5d71979f692f05b69e2ad1fcf689a7 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 18:41:27 +0200 Subject: [PATCH 17/42] Add error checking for self/proxy type combiniations --- examples/simple.rs | 28 +++++----- src/gen.rs | 135 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 122 insertions(+), 41 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 6b4bc93..e58631d 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,23 +5,23 @@ extern crate auto_impl; use auto_impl::auto_impl; -#[auto_impl(&, &mut)] +#[auto_impl(Rc)] trait Foo<'a, X, Y = i32> { - // fn foo(&self); + fn foo(&self); } -#[auto_impl(Box)] -trait MyTrait<'a, T> - where T: AsRef -{ - // type Type1; - // type Type2; - - // fn execute1<'b>(&'a self, arg1: &'b T) -> Result; - // fn execute2(&self) -> Self::Type2; - // fn execute3(self) -> Self::Type1; - // fn execute4() -> &'static str; -} +// #[auto_impl(Box)] +// trait MyTrait<'a, T> +// where T: AsRef +// { +// // type Type1; +// // type Type2; + +// // fn execute1<'b>(&'a self, arg1: &'b T) -> Result; +// // fn execute2(&self) -> Self::Type2; +// // fn execute3(self) -> Self::Type1; +// // fn execute4() -> &'static str; +// } diff --git a/src/gen.rs b/src/gen.rs index 6cfef0f..2e8efd9 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -1,3 +1,4 @@ +use proc_macro::Span; use proc_macro2::{ TokenStream as TokenStream2, Span as Span2, @@ -8,7 +9,10 @@ use syn::{ }; -use crate::proxy::ProxyType; +use crate::{ + proxy::ProxyType, + spanned::Spanned, +}; const PROXY_TY_PARAM_NAME: &str = "__AutoImplProxyT"; const PROXY_LT_PARAM_NAME: &str = "'__auto_impl_proxy_lifetime"; @@ -116,7 +120,7 @@ fn items( trait_def.items.iter().map(|item| { match item { TraitItem::Const(_) => unimplemented!(), - TraitItem::Method(method) => method_item(proxy_type, method), + TraitItem::Method(method) => method_item(proxy_type, method, trait_def), TraitItem::Type(_) => unimplemented!(), TraitItem::Macro(_) => unimplemented!(), TraitItem::Verbatim(_) => unimplemented!(), @@ -124,13 +128,12 @@ fn items( }).collect() } -fn method_item(proxy_type: &ProxyType, item: &TraitItemMethod) -> Result { - enum SelfType { - None, - Ref, - Mut, - Value, - } +fn method_item( + proxy_type: &ProxyType, + item: &TraitItemMethod, + trait_def: &ItemTrait, +) -> Result { + let sig = &item.sig; let name = &sig.ident; @@ -143,42 +146,120 @@ fn method_item(proxy_type: &ProxyType, item: &TraitItemMethod) -> Result SelfType::None, }; - // Check if this method can be implemented for the given proxy type + // Check self type and proxy type combination + check_receiver_compatible(proxy_type, self_arg, &trait_def.ident, sig.span())?; + + let body = match proxy_type { + ProxyType::Ref | ProxyType::RefMut | ProxyType::Arc | ProxyType::Rc | ProxyType::Box => { + quote! { + (**self).#name(#args) + } + } + ProxyType::Fn | ProxyType::FnMut | ProxyType::FnOnce => { + quote! { + self(#args) + } + } + }; + + + Ok(quote! { #sig { #body }}) +} + + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum SelfType { + None, + Ref, + Mut, + Value, +} + +impl SelfType { + 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) => { + let msg = 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(), + ); + + sig_span + .error(msg) + .span_note(Span::call_site(), "auto-impl for immutable references requested here") + .emit(); + Err(()) } + (ProxyType::RefMut, SelfType::Value) => { + let msg = 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, + ); + + sig_span + .error(msg) + .span_note(Span::call_site(), "auto-impl for mutable references requested here") + .emit(); + Err(()) } + (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" + }; + let msg = 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(), + ); + + sig_span + .error(msg) + .span_note(Span::call_site(), "auto-impl for mutable references requested here") + .emit(); + + Err(()) } + (ProxyType::Fn, _) | (ProxyType::FnMut, _) | (ProxyType::FnOnce, _) => { unimplemented!() } - _ => {} // All other combinations are fine - } - let body = match proxy_type { - ProxyType::Ref | ProxyType::RefMut | ProxyType::Arc | ProxyType::Rc | ProxyType::Box => { - quote! { - (**self).#name(#args) - } - } - ProxyType::Fn | ProxyType::FnMut | ProxyType::FnOnce => { - quote! { - self(#args) - } - } - }; - - - Ok(quote! { #sig { #body }}) + _ => Ok(()), // All other combinations are fine + } } From c7f08583e3928e6ab03caa082c8d78173943e6bd Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Wed, 4 Jul 2018 20:30:11 +0200 Subject: [PATCH 18/42] Finish generation of method impls (except for Fn*) There are probably still bugs, but it looks good for now. Additionally, a bunch of documentation was added. --- examples/simple.rs | 26 ++++++++- src/gen.rs | 135 +++++++++++++++++++++++++++++++++++++++------ src/proxy.rs | 25 ++++++--- 3 files changed, 156 insertions(+), 30 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index e58631d..7f01b6b 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,9 +5,12 @@ extern crate auto_impl; use auto_impl::auto_impl; -#[auto_impl(Rc)] +#[auto_impl(Rc, &, &mut, Box)] trait Foo<'a, X, Y = i32> { - fn foo(&self); + fn foo(&self, x: i32, foo: bool) -> f32; + fn foo2(&self, _s: String) -> bool { + true + } } // #[auto_impl(Box)] @@ -23,6 +26,23 @@ trait Foo<'a, X, Y = i32> { // // fn execute4() -> &'static str; // } +fn do_foo<'a, X, T: Foo<'a, X>>(x: T) { + x.foo(3, true); +} + +struct Bar; +impl<'a> Foo<'a, u32> for Bar { + fn foo(&self, _: i32, _: bool) -> f32 { + 0.0 + } +} -fn main() {} +fn main() { + use std::rc::Rc; + + do_foo(Bar); + do_foo(Rc::new(Bar)); + do_foo(&Bar); + do_foo(&mut Bar); +} diff --git a/src/gen.rs b/src/gen.rs index 2e8efd9..3419931 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -5,7 +5,7 @@ use proc_macro2::{ }; use quote::{TokenStreamExt, ToTokens}; use syn::{ - Ident, Lifetime, ItemTrait, TraitItem, TraitItemMethod, FnArg, + Ident, Lifetime, ItemTrait, TraitItem, TraitItemMethod, FnArg, Pat, PatIdent, }; @@ -14,10 +14,23 @@ use crate::{ 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, @@ -26,7 +39,7 @@ pub(crate) fn gen_impls( // One impl for each proxy type for proxy_type in proxy_types { - let header = header(proxy_type, trait_def); + let header = header(proxy_type, trait_def)?; let items = items(proxy_type, trait_def)?; tokens.append_all(quote! { @@ -37,7 +50,9 @@ pub(crate) fn gen_impls( Ok(tokens.into()) } -fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> TokenStream2 { +/// 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()); // Generate generics for impl positions from trait generics. @@ -108,11 +123,13 @@ fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> TokenStream2 { // Combine everything - quote! { + Ok(quote! { impl<#impl_generics> #trait_path for #self_ty #where_clause - } + }) } +/// Generates the implementation of all items of the given trait. These +/// implementations together are the body of the `impl` block. fn items( proxy_type: &ProxyType, trait_def: &ItemTrait, @@ -128,17 +145,19 @@ fn items( }).collect() } +/// 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 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 name = &sig.ident; - let args = TokenStream2::new(); // TODO - let self_arg = match sig.decl.inputs.iter().next() { Some(FnArg::SelfValue(_)) => SelfType::Value, Some(FnArg::SelfRef(arg)) if arg.mutability.is_none() => SelfType::Ref, @@ -149,20 +168,44 @@ fn method_item( // Check self type and proxy type combination check_receiver_compatible(proxy_type, self_arg, &trait_def.ident, sig.span())?; - let body = match proxy_type { - ProxyType::Ref | ProxyType::RefMut | ProxyType::Arc | ProxyType::Rc | ProxyType::Box => { - quote! { - (**self).#name(#args) + // 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 { + // No receiver + SelfType::None => { + // The proxy type is a reference, smartpointer or Box, but not Fn*. + let proxy_ty = Ident::new(PROXY_TY_PARAM_NAME, Span2::call_site()); + quote! { #proxy_ty::#name(#args) } + } + + // Receiver `self` (by value) + SelfType::Value => { + // The proxy type is either Box or Fn*. + if *proxy_type == ProxyType::Box { + quote! { *self.#name(#args) } + } else { + unimplemented!() } } - ProxyType::Fn | ProxyType::FnMut | ProxyType::FnOnce => { - quote! { - self(#args) + + // `&self` or `&mut self` receiver + SelfType::Ref | SelfType::Mut => { + // The proxy type could be anything in the `Ref` case, and `&mut`, + // Box or Fn* in the `Mut` case. + if proxy_type.is_fn() { + unimplemented!() + } else { + quote! { (**self).#name(#args) } } } }; - + // Combine body with signature + // TODO: maybe add `#[inline]`? Ok(quote! { #sig { #body }}) } @@ -263,3 +306,59 @@ fn check_receiver_compatible( _ => 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 { + arg.pat.span() + .error("\ + argument patterns are not supported by #[auto-impl]. Please use \ + a simple name (not `_`).\ + ") + .emit(); + + return Err(()); + } + } + + // Honestly, I'm not sure what this is. + FnArg::Ignored(ty) => { + ty.span() + .error("cannot auto-impl trait, because this argument is ignored") + .emit(); + + return Err(()); + } + + FnArg::Inferred(_) => { + // This can't happen in today's Rust and it's unlikely to + // change in the near future. + 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) +} diff --git a/src/proxy.rs b/src/proxy.rs index c145d7a..4d14d34 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -21,15 +21,22 @@ pub(crate) enum ProxyType { FnOnce, } -// TODO -// impl ProxyType { -// pub(crate) fn is_ref(&self) -> bool { -// match *self { -// ProxyType::Ref | ProxyType::RefMut => true, -// _ => false, -// } -// } -// } +impl ProxyType { + // TODO + // pub(crate) fn is_ref(&self) -> bool { + // match *self { + // ProxyType::Ref | ProxyType::RefMut => true, + // _ => false, + // } + // } + + 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. /// From 3aa074f3c175c7acd3a3da1e7f90dc0cbfc3ddc8 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 5 Jul 2018 16:28:57 +0200 Subject: [PATCH 19/42] Fix bug in method generation --- src/gen.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index 3419931..23e7adf 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -178,15 +178,15 @@ fn method_item( // No receiver SelfType::None => { // The proxy type is a reference, smartpointer or Box, but not Fn*. - let proxy_ty = Ident::new(PROXY_TY_PARAM_NAME, Span2::call_site()); - quote! { #proxy_ty::#name(#args) } + 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 either Box or Fn*. if *proxy_type == ProxyType::Box { - quote! { *self.#name(#args) } + quote! { (*self).#name(#args) } } else { unimplemented!() } From c47eebd6bc3c8a1e87a95e1e1f7893a5082048bc Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 5 Jul 2018 16:30:19 +0200 Subject: [PATCH 20/42] Add method to generate associated type impls --- examples/simple.rs | 24 ++++++++++++------------ src/gen.rs | 42 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 7f01b6b..7174325 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -13,18 +13,18 @@ trait Foo<'a, X, Y = i32> { } } -// #[auto_impl(Box)] -// trait MyTrait<'a, T> -// where T: AsRef -// { -// // type Type1; -// // type Type2; - -// // fn execute1<'b>(&'a self, arg1: &'b T) -> Result; -// // fn execute2(&self) -> Self::Type2; -// // fn execute3(self) -> Self::Type1; -// // fn execute4() -> &'static str; -// } +#[auto_impl(Box)] +trait MyTrait<'a, T> + where T: AsRef +{ + type Type1; + type Type2; + + fn execute1<'b>(&'a self, arg1: &'b T) -> Result; + fn execute2(&self) -> Self::Type2; + fn execute3(self) -> Self::Type1; + fn execute4() -> &'static str; +} fn do_foo<'a, X, T: Foo<'a, X>>(x: T) { x.foo(3, true); diff --git a/src/gen.rs b/src/gen.rs index 23e7adf..578c823 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -6,6 +6,7 @@ use proc_macro2::{ use quote::{TokenStreamExt, ToTokens}; use syn::{ Ident, Lifetime, ItemTrait, TraitItem, TraitItemMethod, FnArg, Pat, PatIdent, + TraitItemType, }; @@ -137,21 +138,56 @@ fn items( trait_def.items.iter().map(|item| { match item { TraitItem::Const(_) => unimplemented!(), - TraitItem::Method(method) => method_item(proxy_type, method, trait_def), - TraitItem::Type(_) => unimplemented!(), + TraitItem::Method(method) => gen_method_item(proxy_type, method, trait_def), + TraitItem::Type(ty) => gen_type_item(proxy_type, ty, trait_def), TraitItem::Macro(_) => unimplemented!(), TraitItem::Verbatim(_) => unimplemented!(), } }).collect() } +/// Generates the implementation of a 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() { + let msg = 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, + ); + + item.span() + .error(msg) + .span_note(Span::call_site(), "auto-impl for Fn-trait requested here") + .emit(); + + return Err(()); + } + + // 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 method_item( +fn gen_method_item( proxy_type: &ProxyType, item: &TraitItemMethod, trait_def: &ItemTrait, From 891b7b082821eefcbdb040935975ba2c9f18c6a4 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 5 Jul 2018 16:36:36 +0200 Subject: [PATCH 21/42] Forbid macro invocations in trait bodies --- src/gen.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/gen.rs b/src/gen.rs index 578c823..478384b 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -140,7 +140,20 @@ fn items( TraitItem::Const(_) => unimplemented!(), TraitItem::Method(method) => gen_method_item(proxy_type, method, trait_def), TraitItem::Type(ty) => gen_type_item(proxy_type, ty, trait_def), - TraitItem::Macro(_) => unimplemented!(), + 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\ + ") + .span_note(Span::call_site(), "auto-impl requested here") + .emit(); + + Err(()) + }, TraitItem::Verbatim(_) => unimplemented!(), } }).collect() From e4aa40a9b547c732467c47892a7be325d6a0d907 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 5 Jul 2018 16:40:13 +0200 Subject: [PATCH 22/42] Print error when encountering verbatim-item --- src/gen.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/gen.rs b/src/gen.rs index 478384b..a69dae4 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -154,7 +154,16 @@ fn items( Err(()) }, - TraitItem::Verbatim(_) => unimplemented!(), + 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)") + .span_note(Span::call_site(), "auto-impl requested here") + .emit(); + + Err(()) + } } }).collect() } From ce89a3ade8b321587e65e4e57c10720769895fc3 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 5 Jul 2018 16:40:57 +0200 Subject: [PATCH 23/42] Rename `items` to `gen_items` --- src/gen.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index a69dae4..0260129 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -41,7 +41,7 @@ pub(crate) fn gen_impls( // One impl for each proxy type for proxy_type in proxy_types { let header = header(proxy_type, trait_def)?; - let items = items(proxy_type, trait_def)?; + let items = gen_items(proxy_type, trait_def)?; tokens.append_all(quote! { #header { #( #items )* } @@ -131,7 +131,7 @@ fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Result Result, ()> { From cbaa7f4ce09c653cadcdbb663ef301ae4eea7c62 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 5 Jul 2018 17:05:17 +0200 Subject: [PATCH 24/42] Add `SelfType::from_sig` helper method --- src/gen.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index 0260129..2fe19e2 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -6,7 +6,7 @@ use proc_macro2::{ use quote::{TokenStreamExt, ToTokens}; use syn::{ Ident, Lifetime, ItemTrait, TraitItem, TraitItemMethod, FnArg, Pat, PatIdent, - TraitItemType, + TraitItemType, MethodSig, }; @@ -216,12 +216,7 @@ fn gen_method_item( ) -> Result { // Determine the kind of the method, determined by the self type. let sig = &item.sig; - let self_arg = 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, - }; + 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())?; @@ -277,6 +272,15 @@ enum SelfType { } 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, From 26a965def447ee181196484b98415ccad6f0ffc3 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 5 Jul 2018 17:53:41 +0200 Subject: [PATCH 25/42] Add incomplete function to extract Fn-type from trait The Fn-type is not really generated yet, but sanity checks are done. --- src/gen.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/src/gen.rs b/src/gen.rs index 2fe19e2..c923812 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -83,7 +83,10 @@ fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Result { (quote! {}, quote! { : #trait_path }) } - _ => unimplemented!(), + 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` @@ -129,6 +132,83 @@ fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Result 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 { + 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)\ + ") + .span_note(Span::call_site(), "auto-impl requested here") + .emit(); + + return Err(()); + } + + // We checked for `None` above + let method = method.unwrap(); + + // 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, + ); + method.sig.span() + .error(msg) + .span_note(Span::call_site(), "auto-impl requested here") + .emit(); + } + + unimplemented!() +} + /// Generates the implementation of all items of the given trait. These /// implementations together are the body of the `impl` block. fn gen_items( From 593a1d54de792e559fe87bdd8592e3247b1df8fc Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 5 Jul 2018 18:05:00 +0200 Subject: [PATCH 26/42] Finish generation for Fn-traits without args and return type --- src/gen.rs | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index c923812..6a8cbe7 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -167,6 +167,8 @@ fn gen_fn_type_for_trait( // We checked for `None` above let method = method.unwrap(); + + // ======================================================================= // 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) { @@ -206,7 +208,21 @@ fn gen_fn_type_for_trait( .emit(); } - unimplemented!() + // ======================================================================= + // Generate the Fn-type + 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)"), + }; + + let args = TokenStream2::new(); + let ret = TokenStream2::new(); + + Ok(quote! { + #fn_name (#args) #ret + }) } /// Generates the implementation of all items of the given trait. These @@ -308,32 +324,29 @@ fn gen_method_item( // 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, but not Fn*. + // 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 either Box or Fn*. - if *proxy_type == ProxyType::Box { - quote! { (*self).#name(#args) } - } else { - unimplemented!() - } + // 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`, - // Box or Fn* in the `Mut` case. - if proxy_type.is_fn() { - unimplemented!() - } else { - quote! { (**self).#name(#args) } - } + // The proxy type could be anything in the `Ref` case, and `&mut` + // or Box in the `Mut` case. + quote! { (**self).#name(#args) } } }; @@ -442,7 +455,9 @@ fn check_receiver_compatible( (ProxyType::Fn, _) | (ProxyType::FnMut, _) | (ProxyType::FnOnce, _) => { - unimplemented!() + // 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 From da4acfc1ffd12a4e1190aafe9b09b9d190f770ef Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 5 Jul 2018 18:16:33 +0200 Subject: [PATCH 27/42] Add signature modifier check for Fn traits --- src/gen.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/gen.rs b/src/gen.rs index 6a8cbe7..79f6ede 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -166,6 +166,51 @@ fn gen_fn_type_for_trait( // 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 { + let msg = format!( + "the trait '{}' cannot be implemented for Fn-traits: const methods are not allowed", + trait_def.ident, + ); + + const_token.span() + .error(msg) + .span_note(Span::call_site(), "auto-impl requested here") + .emit(); + + return Err(()); + } + + if let Some(unsafe_token) = &sig.unsafety { + let msg = format!( + "the trait '{}' cannot be implemented for Fn-traits: unsafe methods are not allowed", + trait_def.ident, + ); + + unsafe_token.span() + .error(msg) + .span_note(Span::call_site(), "auto-impl requested here") + .emit(); + + return Err(()); + } + + if let Some(abi_token) = &sig.abi { + let msg = format!( + "the trait '{}' cannot be implemented for Fn-traits: custom ABIs are not allowed", + trait_def.ident, + ); + + abi_token.span() + .error(msg) + .span_note(Span::call_site(), "auto-impl requested here") + .emit(); + + return Err(()); + } // ======================================================================= @@ -209,7 +254,9 @@ fn gen_fn_type_for_trait( } // ======================================================================= - // Generate the Fn-type + // 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 }, From 89f39eb772ceede0ca63019e2b60f8e5d915eff6 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 5 Jul 2018 18:32:30 +0200 Subject: [PATCH 28/42] Add extension trait to help with recurring notes on Diagnostics --- src/gen.rs | 187 +++++++++++++++++++++++------------------------------ 1 file changed, 80 insertions(+), 107 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index 79f6ede..29aba28 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -1,4 +1,4 @@ -use proc_macro::Span; +use proc_macro::{Diagnostic, Span}; use proc_macro2::{ TokenStream as TokenStream2, Span as Span2, @@ -153,15 +153,12 @@ fn gen_fn_type_for_trait( // If this requirement is not satisfied, we emit an error. if method.is_none() || trait_def.items.len() > 1 { - trait_def.span() + 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)\ ") - .span_note(Span::call_site(), "auto-impl requested here") - .emit(); - - return Err(()); + .emit_with_attr_note(); } // We checked for `None` above @@ -171,45 +168,32 @@ fn gen_fn_type_for_trait( // Check for forbidden modifier of the method if let Some(const_token) = sig.constness { - let msg = format!( - "the trait '{}' cannot be implemented for Fn-traits: const methods are not allowed", - trait_def.ident, - ); - - const_token.span() - .error(msg) - .span_note(Span::call_site(), "auto-impl requested here") - .emit(); - - return Err(()); + 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 { - let msg = format!( - "the trait '{}' cannot be implemented for Fn-traits: unsafe methods are not allowed", - trait_def.ident, - ); - - unsafe_token.span() - .error(msg) - .span_note(Span::call_site(), "auto-impl requested here") - .emit(); - - return Err(()); + 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 { - let msg = format!( - "the trait '{}' cannot be implemented for Fn-traits: custom ABIs are not allowed", - trait_def.ident, - ); - - abi_token.span() - .error(msg) - .span_note(Span::call_site(), "auto-impl requested here") - .emit(); - - return Err(()); + 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(); } @@ -247,10 +231,10 @@ fn gen_fn_type_for_trait( receiver, allowed, ); - method.sig.span() + + return method.sig.span() .error(msg) - .span_note(Span::call_site(), "auto-impl requested here") - .emit(); + .emit_with_attr_note(); } // ======================================================================= @@ -292,20 +276,14 @@ fn gen_items( traits with macro invocations in their bodies are not \ supported by auto_impl\ ") - .span_note(Span::call_site(), "auto-impl requested here") - .emit(); - - Err(()) + .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)") - .span_note(Span::call_site(), "auto-impl requested here") - .emit(); - - Err(()) + .emit_with_attr_note() } } }).collect() @@ -323,18 +301,14 @@ fn gen_type_item( ) -> Result { // A trait with associated types cannot be implemented for Fn* types. if proxy_type.is_fn() { - let msg = 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, - ); - - item.span() - .error(msg) - .span_note(Span::call_site(), "auto-impl for Fn-trait requested here") - .emit(); - - return Err(()); + 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. @@ -442,35 +416,26 @@ fn check_receiver_compatible( match (proxy_type, self_arg) { (ProxyType::Ref, SelfType::Mut) | (ProxyType::Ref, SelfType::Value) => { - let msg = 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(), - ); - sig_span - .error(msg) - .span_note(Span::call_site(), "auto-impl for immutable references requested here") - .emit(); - - Err(()) + .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) => { - let msg = 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, - ); - sig_span - .error(msg) - .span_note(Span::call_site(), "auto-impl for mutable references requested here") - .emit(); - - Err(()) + .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) | @@ -483,20 +448,16 @@ fn check_receiver_compatible( "Arc" }; - let msg = 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(), - ); - sig_span - .error(msg) - .span_note(Span::call_site(), "auto-impl for mutable references requested here") - .emit(); - - Err(()) + .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, _) | @@ -532,24 +493,20 @@ fn get_arg_list(inputs: impl Iterator) -> Result { - ty.span() + return ty.span() .error("cannot auto-impl trait, because this argument is ignored") - .emit(); - - return Err(()); + .emit_with_attr_note(); } FnArg::Inferred(_) => { @@ -566,3 +523,19 @@ fn get_arg_list(inputs: impl Iterator) -> Result(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(()) + } +} From 118ab60bc0c71ae11773bc29ab06d6b525effc2e Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Thu, 5 Jul 2018 18:47:34 +0200 Subject: [PATCH 29/42] Finish generation of Fn-type (without correct lifetimes) --- examples/simple.rs | 49 ++++++++++++++++++---------------------------- src/gen.rs | 43 +++++++++++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 7174325..edfbc28 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,44 +5,33 @@ extern crate auto_impl; use auto_impl::auto_impl; -#[auto_impl(Rc, &, &mut, Box)] -trait Foo<'a, X, Y = i32> { +#[auto_impl(Fn)] +trait Foo { fn foo(&self, x: i32, foo: bool) -> f32; - fn foo2(&self, _s: String) -> bool { - true - } + // fn foo2(&self, _s: String) -> bool { + // true + // } } -#[auto_impl(Box)] -trait MyTrait<'a, T> - where T: AsRef -{ - type Type1; - type Type2; - - fn execute1<'b>(&'a self, arg1: &'b T) -> Result; - fn execute2(&self) -> Self::Type2; - fn execute3(self) -> Self::Type1; - fn execute4() -> &'static str; -} +// #[auto_impl(Box)] +// trait MyTrait<'a, T> +// where T: AsRef +// { +// type Type1; +// type Type2; + +// fn execute1<'b>(&'a self, arg1: &'b T) -> Result; +// fn execute2(&self) -> Self::Type2; +// fn execute3(self) -> Self::Type1; +// fn execute4() -> &'static str; +// } -fn do_foo<'a, X, T: Foo<'a, X>>(x: T) { +fn do_foo(x: T) { x.foo(3, true); } -struct Bar; -impl<'a> Foo<'a, u32> for Bar { - fn foo(&self, _: i32, _: bool) -> f32 { - 0.0 - } -} fn main() { - use std::rc::Rc; - - do_foo(Bar); - do_foo(Rc::new(Bar)); - do_foo(&Bar); - do_foo(&mut Bar); + do_foo(|_: i32, _: bool| 0.0); } diff --git a/src/gen.rs b/src/gen.rs index 29aba28..329fc7a 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -248,11 +248,38 @@ fn gen_fn_type_for_trait( _ => panic!("internal error in auto_impl (function contract violation)"), }; - let args = TokenStream2::new(); - let ret = TokenStream2::new(); + // The return type + let ret = &sig.decl.output; + + // 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!("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"); + } + + // We skipped the receiver already + FnArg::SelfRef(_) | FnArg::SelfValue(_) => {} + } + } + Ok(quote! { - #fn_name (#args) #ret + #fn_name (#arg_types) #ret }) } @@ -503,15 +530,13 @@ fn get_arg_list(inputs: impl Iterator) -> Result { - return ty.span() - .error("cannot auto-impl trait, because this argument is ignored") - .emit_with_attr_note(); + 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(_) => { - // This can't happen in today's Rust and it's unlikely to - // change in the near future. panic!("argument with inferred type in trait method"); } From 11b9100452044e4bf51ec092a05744ffc8e9ad05 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Sat, 7 Jul 2018 16:59:17 +0200 Subject: [PATCH 30/42] Respect local lifetimes and declare them as HRTBs --- examples/simple.rs | 6 +++--- src/gen.rs | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index edfbc28..78da541 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -7,7 +7,7 @@ use auto_impl::auto_impl; #[auto_impl(Fn)] trait Foo { - fn foo(&self, x: i32, foo: bool) -> f32; + fn foo<'a>(&self, x: &'a i32) -> f32; // fn foo2(&self, _s: String) -> bool { // true // } @@ -27,11 +27,11 @@ trait Foo { // } fn do_foo(x: T) { - x.foo(3, true); + x.foo(&3); } fn main() { - do_foo(|_: i32, _: bool| 0.0); + do_foo(|_: &i32| 0.0); } diff --git a/src/gen.rs b/src/gen.rs index 329fc7a..09b6a07 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -251,6 +251,20 @@ fn gen_fn_type_for_trait( // 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. + // + // 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(); @@ -263,7 +277,7 @@ fn gen_fn_type_for_trait( // Honestly, I'm not sure what this is. FnArg::Ignored(_) => { - panic!("ignored argument encountered (auto_impl is confused)"); + panic!("unexpected ignored argument (auto_impl is confused)"); } // This can't happen in today's Rust and it's unlikely to change in @@ -279,7 +293,7 @@ fn gen_fn_type_for_trait( Ok(quote! { - #fn_name (#arg_types) #ret + for< #(#local_lifetimes),* > #fn_name (#arg_types) #ret }) } From 15c4e80f9b0b458cbd6b49fa33eeabaa26ae1c1d Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Sat, 7 Jul 2018 17:11:21 +0200 Subject: [PATCH 31/42] Formatting, mostly rustfmt --- src/gen.rs | 82 +++++++++++++++++++++++----------------------------- src/lib.rs | 7 +---- src/proxy.rs | 20 ++++--------- 3 files changed, 42 insertions(+), 67 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index 09b6a07..90d8ea2 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -1,19 +1,12 @@ use proc_macro::{Diagnostic, Span}; -use proc_macro2::{ - TokenStream as TokenStream2, - Span as Span2, -}; -use quote::{TokenStreamExt, ToTokens}; +use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; +use quote::{ToTokens, TokenStreamExt}; use syn::{ - Ident, Lifetime, ItemTrait, TraitItem, TraitItemMethod, FnArg, Pat, PatIdent, - TraitItemType, MethodSig, + FnArg, Ident, ItemTrait, Lifetime, MethodSig, Pat, PatIdent, TraitItem, TraitItemMethod, + TraitItemType, }; - -use crate::{ - proxy::ProxyType, - spanned::Spanned, -}; +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. @@ -80,12 +73,10 @@ fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Result { - (quote! {}, quote! { : #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 }) + (quote!{}, quote! { : #fn_bound }) } }; @@ -96,13 +87,13 @@ fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Result>(); - tts.pop(); // the closing `>` + 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() { - quote! {} + quote!{} } else { quote! { , } }; @@ -154,10 +145,10 @@ fn gen_fn_type_for_trait( // 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)\ - ") + .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(); } @@ -214,9 +205,11 @@ fn gen_fn_type_for_trait( } // 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)")) - } + (SelfType::Ref, ProxyType::FnMut) => Some(( + "`FnMut`", + "a `&self`", + " (only `self` and `&mut self` are allowed)", + )), // Other combinations are fine _ => None, @@ -232,9 +225,7 @@ fn gen_fn_type_for_trait( allowed, ); - return method.sig.span() - .error(msg) - .emit_with_attr_note(); + return method.sig.span().error(msg).emit_with_attr_note(); } // ======================================================================= @@ -313,10 +304,10 @@ fn gen_items( // 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\ - ") + .error( + "traits with macro invocations in their bodies are not \ + supported by auto_impl" + ) .emit_with_attr_note() }, TraitItem::Verbatim(v) => { @@ -455,8 +446,8 @@ fn check_receiver_compatible( sig_span: Span, ) -> Result<(), ()> { match (proxy_type, self_arg) { - (ProxyType::Ref, SelfType::Mut) | - (ProxyType::Ref, SelfType::Value) => { + (ProxyType::Ref, SelfType::Mut) + | (ProxyType::Ref, SelfType::Value) => { sig_span .error(format!( "the trait `{}` cannot be auto-implemented for immutable references, because \ @@ -479,10 +470,10 @@ fn check_receiver_compatible( .emit_with_attr_note() } - (ProxyType::Rc, SelfType::Mut) | - (ProxyType::Rc, SelfType::Value) | - (ProxyType::Arc, SelfType::Mut) | - (ProxyType::Arc, SelfType::Value) => { + (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 { @@ -501,9 +492,7 @@ fn check_receiver_compatible( .emit_with_attr_note() } - (ProxyType::Fn, _) | - (ProxyType::FnMut, _) | - (ProxyType::FnOnce, _) => { + (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(()) @@ -530,15 +519,16 @@ fn get_arg_list(inputs: impl Iterator) -> Result) -> Result) -> Result Date: Sat, 7 Jul 2018 18:09:52 +0200 Subject: [PATCH 32/42] Add two simple examples --- examples/greet_closure.rs | 41 ++++++++++++++++++++++++++ examples/refs.rs | 61 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 examples/greet_closure.rs create mode 100644 examples/refs.rs diff --git a/examples/greet_closure.rs b/examples/greet_closure.rs new file mode 100644 index 0000000..94f5a07 --- /dev/null +++ b/examples/greet_closure.rs @@ -0,0 +1,41 @@ +#![feature(proc_macro)] + +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..875ac7d --- /dev/null +++ b/examples/refs.rs @@ -0,0 +1,61 @@ +#![feature(proc_macro)] + +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 { + type Out: Display; + fn display_at(&self, index: usize) -> Option<&Self::Out>; +} + +impl DisplayCollection for Vec { + 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> +} From 2d982c0c4878cd9e2bbb379221ec573a76c9f21b Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Sat, 7 Jul 2018 18:10:48 +0200 Subject: [PATCH 33/42] Fix two bugs in impl-generation --- src/gen.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index 90d8ea2..d030efd 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -48,6 +48,7 @@ pub(crate) fn gen_impls( /// 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(); @@ -70,8 +71,7 @@ fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Result { - let lifetime = &Lifetime::new(PROXY_LT_PARAM_NAME, Span2::call_site()); - (quote! { #lifetime, }, quote! { : #lifetime + #trait_path }) + (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 => { @@ -88,11 +88,11 @@ fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Result>(); tts.pop(); // the closing `>` - params.append_all(tts); + 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() { + let comma = if params.is_empty() || tts.is_empty() { quote!{} } else { quote! { , } @@ -106,8 +106,8 @@ fn header(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Result quote! { &'a #proxy_ty_param }, - ProxyType::RefMut => quote! { &'a mut #proxy_ty_param }, + 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> }, @@ -253,6 +253,10 @@ fn gen_fn_type_for_trait( // 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(); From f344b1e21d60f7ec3558e2a5d687082ed4d7ec20 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Sat, 7 Jul 2018 18:11:04 +0200 Subject: [PATCH 34/42] Remove useless code --- src/proxy.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/proxy.rs b/src/proxy.rs index bd5cae3..582ae14 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -16,14 +16,6 @@ pub(crate) enum ProxyType { } impl ProxyType { - // TODO - // pub(crate) fn is_ref(&self) -> bool { - // match *self { - // ProxyType::Ref | ProxyType::RefMut => true, - // _ => false, - // } - // } - pub(crate) fn is_fn(&self) -> bool { match *self { ProxyType::Fn | ProxyType::FnMut | ProxyType::FnOnce => true, From e06e86342e38a606c19bb8be3bf499ba0550d931 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Sat, 7 Jul 2018 18:15:30 +0200 Subject: [PATCH 35/42] Remove failing test and leave comment to explain why --- src/proxy.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/proxy.rs b/src/proxy.rs index 582ae14..ae48bb3 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -136,15 +136,12 @@ fn eat_type(iter: &mut Peekable) -> Result Date: Sat, 7 Jul 2018 18:16:25 +0200 Subject: [PATCH 36/42] Remove testing example --- examples/simple.rs | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 examples/simple.rs diff --git a/examples/simple.rs b/examples/simple.rs deleted file mode 100644 index 78da541..0000000 --- a/examples/simple.rs +++ /dev/null @@ -1,37 +0,0 @@ -#![feature(proc_macro)] - -extern crate auto_impl; - -use auto_impl::auto_impl; - - -#[auto_impl(Fn)] -trait Foo { - fn foo<'a>(&self, x: &'a i32) -> f32; - // fn foo2(&self, _s: String) -> bool { - // true - // } -} - -// #[auto_impl(Box)] -// trait MyTrait<'a, T> -// where T: AsRef -// { -// type Type1; -// type Type2; - -// fn execute1<'b>(&'a self, arg1: &'b T) -> Result; -// fn execute2(&self) -> Self::Type2; -// fn execute3(self) -> Self::Type1; -// fn execute4() -> &'static str; -// } - -fn do_foo(x: T) { - x.foo(&3); -} - - - -fn main() { - do_foo(|_: &i32| 0.0); -} From 1322d808950c9f63bd37ac0eecbca8f4fb4306d5 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Sat, 7 Jul 2018 18:16:55 +0200 Subject: [PATCH 37/42] Remove debug output and improve comments in lib.rs --- src/lib.rs | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 557da65..d92c30e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,16 +25,21 @@ pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream { // Try to parse the token stream from the attribute to get a list of // proxy types. let proxy_types = proxy::parse_types(args)?; - // println!("Proxy types: {:?}", proxy_types); - // Try to parse the + // 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()) { - Ok(trait_def) => { - // println!("{:#?}", trait_def); - - let impls = gen::gen_impls(&proxy_types, &trait_def)?; - Ok(impls) - } + // 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) @@ -45,15 +50,9 @@ pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream { Err(()) } } - }().unwrap_or(TokenStream::new()); + }().unwrap_or(TokenStream::new()); // If an error occured, we don't add any tokens. // Combine the original token stream with the additional one containing the // generated impls (or nothing if an error occured). - let out = vec![input, impls].into_iter().collect(); - - println!("--------------------"); - println!("{}", out); - println!("--------------------"); - - out + vec![input, impls].into_iter().collect() } From 08ebf335a227bf4a3991a0126bc70f5995ba2c5a Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Sat, 7 Jul 2018 18:18:12 +0200 Subject: [PATCH 38/42] Remove `extra-traits` feature from syn (only used for debugging) --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index db4bd7f..f64e793 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,5 +19,4 @@ proc-macro = true [dependencies] proc-macro2 = { version = "0.4.6", features = ["nightly"] } quote = "0.6.3" -# TODO: remove "extra-traits", it's just for debugging -syn = { version = "0.14.4", features = ["full", "extra-traits"] } +syn = { version = "0.14.4", features = ["full"] } From 565190e146ab0206755a66a49581f5403f924d60 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Sat, 7 Jul 2018 18:39:30 +0200 Subject: [PATCH 39/42] Add minimal documentation --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index d92c30e..d037d26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +//! A proc-macro attribute for automatically implementing a trait for +//! references, some common smart pointers and closures. + #![feature(crate_in_paths)] #![feature(extern_prelude)] #![feature(in_band_lifetimes)] @@ -17,6 +20,7 @@ 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 From eb1e38e2edbcd88ecac722c40a95cd2875f3a1aa Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Sat, 7 Jul 2018 19:00:49 +0200 Subject: [PATCH 40/42] Add my name to the list of authors I figured that I changed enough lines, so I might as well add my name :P --- Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f64e793..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" From fb05e56ae739779658ac966a293e97bc1e5d6505 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Sun, 8 Jul 2018 13:44:44 +0200 Subject: [PATCH 41/42] Add support for associated consts --- examples/refs.rs | 3 +++ src/gen.rs | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/examples/refs.rs b/examples/refs.rs index 875ac7d..2d631a7 100644 --- a/examples/refs.rs +++ b/examples/refs.rs @@ -30,11 +30,14 @@ use auto_impl::auto_impl; /// ``` #[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) diff --git a/src/gen.rs b/src/gen.rs index d030efd..bf2ddd3 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -3,7 +3,7 @@ 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, + TraitItemType, TraitItemConst, }; use crate::{proxy::ProxyType, spanned::Spanned}; @@ -300,7 +300,7 @@ fn gen_items( ) -> Result, ()> { trait_def.items.iter().map(|item| { match item { - TraitItem::Const(_) => unimplemented!(), + 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) => { @@ -325,7 +325,39 @@ fn gen_items( }).collect() } -/// Generates the implementation of a associated type item described by `item`. +/// 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 From 624076f8a224a530ebe24b97a873da66497eed5b Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Tue, 17 Jul 2018 12:32:05 +0200 Subject: [PATCH 42/42] Update after proc_macro stabilization The feature `proc_macro` was recently stabilized [1]. This has two effects: - The `proc_macro` feature gate is replaced by finer grained feature gates - Using this crate now doesn't need `#![feature(proc_macro)]` but `#![feature(use_extern_macros)]` [1]: https://github.com/rust-lang/rust/pull/52081 --- compile_test/src/main.rs | 2 +- examples/greet_closure.rs | 2 +- examples/refs.rs | 2 +- src/lib.rs | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) 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 index 94f5a07..8f3db35 100644 --- a/examples/greet_closure.rs +++ b/examples/greet_closure.rs @@ -1,4 +1,4 @@ -#![feature(proc_macro)] +#![feature(use_extern_macros)] extern crate auto_impl; diff --git a/examples/refs.rs b/examples/refs.rs index 2d631a7..0845ee9 100644 --- a/examples/refs.rs +++ b/examples/refs.rs @@ -1,4 +1,4 @@ -#![feature(proc_macro)] +#![feature(use_extern_macros)] extern crate auto_impl; diff --git a/src/lib.rs b/src/lib.rs index d037d26..9c83b14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,8 @@ #![feature(crate_in_paths)] #![feature(extern_prelude)] #![feature(in_band_lifetimes)] -#![feature(proc_macro)] +#![feature(proc_macro_span)] +#![feature(proc_macro_diagnostic)] extern crate proc_macro;