From 5e85cb2016f77f0c15c3c16f298e6bcd22235340 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Sun, 28 Jan 2024 10:08:00 +1000 Subject: [PATCH] port error handling to syn::Error --- Cargo.toml | 1 - src/attr.rs | 93 ++-- src/gen.rs | 398 +++++++++--------- src/lib.rs | 48 +-- src/proxy.rs | 50 +-- tests/compile-fail/attr_on_enum.stderr | 12 +- tests/compile-fail/attr_on_fn.stderr | 12 +- tests/compile-fail/attr_on_impl_block.stderr | 12 +- tests/compile-fail/attr_on_struct.stderr | 12 +- tests/compile-fail/attr_on_type.stderr | 12 +- tests/compile-fail/attr_on_unit_struct.stderr | 12 +- tests/compile-fail/fn_associated_const.stderr | 27 -- tests/compile-fail/fn_associated_type.stderr | 27 -- tests/compile-fail/fn_generics.stderr | 13 +- tests/compile-fail/fn_multiple_methods.stderr | 8 - ...keep_default_for_on_required_method.stderr | 11 - tests/compile-fail/mut_self_for_arc.stderr | 4 +- .../mut_self_for_immutable_ref.stderr | 4 +- tests/compile-fail/mut_self_for_rc.stderr | 4 +- .../value_self_for_immutable_ref.stderr | 4 +- .../value_self_for_mutable_ref.stderr | 4 +- 21 files changed, 333 insertions(+), 435 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index df218b6..a865292 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,6 @@ proc-macro = true proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["full", "visit", "visit-mut"] } -proc-macro-error = "1.0.0" [dev-dependencies] trybuild = "1" diff --git a/src/attr.rs b/src/attr.rs index 4be0eec..e00c191 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -2,19 +2,18 @@ //! attached to trait items. use proc_macro2::{Delimiter, TokenTree}; -use proc_macro_error::{abort, emit_error}; use syn::{ spanned::Spanned, visit_mut::{visit_item_trait_mut, VisitMut}, - Attribute, Meta, TraitItem, + Attribute, Error, Meta, TraitItem, }; use crate::proxy::{parse_types, ProxyType}; /// Removes all `#[auto_impl]` attributes that are attached to methods of the /// given trait. -pub(crate) fn remove_our_attrs(trait_def: &mut syn::ItemTrait) { - struct AttrRemover; +pub(crate) fn remove_our_attrs(trait_def: &mut syn::ItemTrait) -> syn::Result<()> { + struct AttrRemover(syn::Result<()>); impl VisitMut for AttrRemover { fn visit_trait_item_mut(&mut self, item: &mut TraitItem) { let item_span = item.span(); @@ -23,26 +22,46 @@ pub(crate) fn remove_our_attrs(trait_def: &mut syn::ItemTrait) { TraitItem::Const(c) => (&mut c.attrs, false), TraitItem::Type(t) => (&mut t.attrs, false), TraitItem::Macro(m) => (&mut m.attrs, false), - _ => abort!( - item.span(), - "encountered unexpected `TraitItem`, cannot handle that, sorry!"; - note = "auto-impl supports only methods, consts, types and macros currently"; - ), + _ => { + let err = syn::Error::new( + item.span(), + "encountered unexpected `TraitItem`, cannot handle that, sorry!", + ); + + if let Err(ref mut current_err) = self.0 { + current_err.combine(err); + } else { + self.0 = Err(err); + }; + + return; + } }; // Make sure non-methods do not have our attributes. if !is_method && attrs.iter().any(is_our_attr) { - emit_error!( + let err = syn::Error::new( item_span, "`#[auto_impl]` attributes are only allowed on methods", ); + + if let Err(ref mut current_err) = self.0 { + current_err.combine(err); + } else { + self.0 = Err(err); + }; + + return; } attrs.retain(|a| !is_our_attr(a)); } } - visit_item_trait_mut(&mut AttrRemover, trait_def); + let mut visitor = AttrRemover(Ok(())); + visit_item_trait_mut(&mut visitor, trait_def); + + visitor.0 } /// Checks if the given attribute is "our" attribute. That means that it's path @@ -55,7 +74,7 @@ pub(crate) fn is_our_attr(attr: &Attribute) -> bool { /// attributes. If it's invalid, an error is emitted and `Err(())` is returned. /// You have to make sure that `attr` is one of our attrs with `is_our_attr` /// before calling this function! -pub(crate) fn parse_our_attr(attr: &Attribute) -> Result { +pub(crate) fn parse_our_attr(attr: &Attribute) -> syn::Result { assert!(is_our_attr(attr)); // Get the body of the attribute (which has to be a ground, because we @@ -64,8 +83,10 @@ pub(crate) fn parse_our_attr(attr: &Attribute) -> Result { let body = match &attr.meta { Meta::List(list) => list.tokens.clone(), _ => { - emit_error!(attr.span(), "expected single group delimited by `()`"); - return Err(()); + return Err(Error::new( + attr.span(), + "expected single group delimited by `()`", + )); } }; @@ -75,12 +96,13 @@ pub(crate) fn parse_our_attr(attr: &Attribute) -> Result { let name = match it.next() { Some(TokenTree::Ident(x)) => x, Some(other) => { - emit_error!(other.span(), "expected ident, found '{}'", other); - return Err(()); + return Err(Error::new( + other.span(), + format_args!("expected ident, found '{}'", other), + )); } None => { - emit_error!(attr.span(), "expected ident, found nothing"); - return Err(()); + return Err(Error::new(attr.span(), "expected ident, found nothing")); } }; @@ -89,21 +111,22 @@ pub(crate) fn parse_our_attr(attr: &Attribute) -> Result { let params = match it.next() { Some(TokenTree::Group(ref g)) if g.delimiter() == Delimiter::Parenthesis => g.stream(), Some(other) => { - emit_error!( + return Err(Error::new( other.span(), - "expected arguments for '{}' in parenthesis `()`, found `{}`", - name, - other, - ); - return Err(()); + format_args!( + "expected arguments for '{}' in parenthesis `()`, found `{}`", + name, other + ), + )); } None => { - emit_error!( + return Err(Error::new( body.span(), - "expected arguments for '{}' in parenthesis `()`, found nothing", - name, - ); - return Err(()); + format_args!( + "expected arguments for '{}' in parenthesis `()`, found nothing", + name, + ), + )); } }; @@ -112,11 +135,13 @@ pub(crate) fn parse_our_attr(attr: &Attribute) -> Result { let proxy_types = parse_types(params.into()); OurAttr::KeepDefaultFor(proxy_types) } else { - emit_error!( - name.span(), "invalid attribute '{}'", name; - note = "only `keep_default_for` is supported"; - ); - return Err(()); + return Err(Error::new( + name.span(), + format_args!( + "invalid attribute '{}'; only `keep_default_for` is supported", + name + ), + )); }; Ok(out) diff --git a/src/gen.rs b/src/gen.rs index 92df3f0..9a6c73a 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -1,10 +1,10 @@ use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; -use proc_macro_error::{abort, emit_error}; use quote::{ToTokens, TokenStreamExt}; use syn::{ - punctuated::Punctuated, spanned::Spanned, Attribute, FnArg, GenericParam, Ident, ItemTrait, - Lifetime, Pat, PatIdent, PatType, ReturnType, Signature, Token, TraitBound, TraitBoundModifier, - TraitItem, TraitItemConst, TraitItemFn, TraitItemType, Type, TypeParamBound, WherePredicate, + punctuated::Punctuated, spanned::Spanned, Attribute, Error, FnArg, GenericParam, Ident, + ItemTrait, Lifetime, Pat, PatIdent, PatType, ReturnType, Signature, Token, TraitBound, + TraitBoundModifier, TraitItem, TraitItemConst, TraitItemFn, TraitItemType, Type, + TypeParamBound, WherePredicate, }; use crate::{ @@ -15,15 +15,18 @@ use crate::{ /// 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) -> TokenStream2 { +pub(crate) fn gen_impls( + proxy_types: &[ProxyType], + trait_def: &syn::ItemTrait, +) -> syn::Result { let mut tokens = TokenStream2::new(); let (proxy_ty_param, proxy_lt_param) = find_suitable_param_names(trait_def); // One impl for each proxy type for proxy_type in proxy_types { - let header = gen_header(proxy_type, trait_def, &proxy_ty_param, &proxy_lt_param); - let items = gen_items(proxy_type, trait_def, &proxy_ty_param); + let header = gen_header(proxy_type, trait_def, &proxy_ty_param, &proxy_lt_param)?; + let items = gen_items(proxy_type, trait_def, &proxy_ty_param)?; if let ProxyType::Box | ProxyType::Rc | ProxyType::Arc = proxy_type { tokens.append_all(quote! { @@ -41,7 +44,7 @@ pub(crate) fn gen_impls(proxy_types: &[ProxyType], trait_def: &syn::ItemTrait) - } } - tokens + Ok(tokens) } /// Generates the header of the impl of the given trait for the given proxy @@ -51,7 +54,7 @@ fn gen_header( trait_def: &ItemTrait, proxy_ty_param: &Ident, proxy_lt_param: &Lifetime, -) -> TokenStream2 { +) -> syn::Result { // Generate generics for impl positions from trait generics. let (impl_generics, trait_generics, where_clause) = trait_def.generics.split_for_impl(); @@ -72,7 +75,7 @@ fn gen_header( // Determine whether we can add a `?Sized` relaxation to allow trait // objects. We can do that as long as there is no method that has a // `self` by value receiver and no `where Self: Sized` bound. - let sized_required = trait_def + let methods = trait_def .items .iter() // Only interested in methods @@ -82,69 +85,75 @@ fn gen_header( } else { None } - }) - // We also ignore methods that we will not override. In the case of - // invalid attributes it is save to assume default behavior. - .filter(|m| !should_keep_default_for(m, proxy_type)) - .any(|m| { - // Check if there is a `Self: Sized` bound on the method. - let self_is_bounded_sized = m - .sig - .generics - .where_clause - .iter() - .flat_map(|wc| &wc.predicates) - .filter_map(|pred| { - if let WherePredicate::Type(p) = pred { - Some(p) - } else { - None - } - }) - .any(|pred| { - // Check if the type is `Self` - match &pred.bounded_ty { - Type::Path(p) if p.path.is_ident("Self") => { - // Check if the bound contains `Sized` - pred.bounds.iter().any(|b| match b { - TypeParamBound::Trait(TraitBound { - modifier: TraitBoundModifier::None, - path, - .. - }) => path.is_ident("Sized"), - _ => false, - }) - } - _ => false, - } - }); + }); - // Check if the first parameter is `self` by value. In that - // case, we might require `Self` to be `Sized`. - let self_value_param = match m.sig.inputs.first() { - Some(FnArg::Receiver(receiver)) => receiver.reference.is_none(), - _ => false, - }; + let mut sized_required = false; + for m in methods { + if should_keep_default_for(m, proxy_type)? { + continue; + } - // Check if return type is `Self` - let self_value_return = match &m.sig.output { - ReturnType::Type(_, t) => { - if let Type::Path(p) = &**t { - p.path.is_ident("Self") - } else { - false + // Check if there is a `Self: Sized` bound on the method. + let self_is_bounded_sized = m + .sig + .generics + .where_clause + .iter() + .flat_map(|wc| &wc.predicates) + .filter_map(|pred| { + if let WherePredicate::Type(p) = pred { + Some(p) + } else { + None + } + }) + .any(|pred| { + // Check if the type is `Self` + match &pred.bounded_ty { + Type::Path(p) if p.path.is_ident("Self") => { + // Check if the bound contains `Sized` + pred.bounds.iter().any(|b| match b { + TypeParamBound::Trait(TraitBound { + modifier: TraitBoundModifier::None, + path, + .. + }) => path.is_ident("Sized"), + _ => false, + }) } + _ => false, } - _ => false, - }; + }); - // TODO: check for `Self` parameter in any other argument. + // Check if the first parameter is `self` by value. In that + // case, we might require `Self` to be `Sized`. + let self_value_param = match m.sig.inputs.first() { + Some(FnArg::Receiver(receiver)) => receiver.reference.is_none(), + _ => false, + }; - // If for this method, `Self` is used in a position that - // requires `Self: Sized` or this bound is added explicitly, we - // cannot add the `?Sized` relaxation to the impl body. - self_value_param || self_value_return || self_is_bounded_sized - }); + // Check if return type is `Self` + let self_value_return = match &m.sig.output { + ReturnType::Type(_, t) => { + if let Type::Path(p) = &**t { + p.path.is_ident("Self") + } else { + false + } + } + _ => false, + }; + + // TODO: check for `Self` parameter in any other argument. + + // If for this method, `Self` is used in a position that + // requires `Self: Sized` or this bound is added explicitly, we + // cannot add the `?Sized` relaxation to the impl body. + if self_value_param || self_value_return || self_is_bounded_sized { + sized_required = true; + break; + } + } let relaxation = if sized_required { quote! {} @@ -157,7 +166,7 @@ fn gen_header( // information, but in short: there is no better solution. Method where // clauses with `Self: Foo` force us to add `T: Foo` to the impl // header, as we otherwise cannot generate a valid impl block. - let additional_bounds = trait_def + let methods = trait_def .items .iter() // Only interested in methods @@ -167,12 +176,15 @@ fn gen_header( } else { None } - }) - // We also ignore methods that we will not override. In the case of - // invalid attributes it is save to assume default behavior. - .filter(|m| !should_keep_default_for(m, proxy_type)) - // Exact all relevant bounds - .flat_map(|m| { + }); + + let mut additional_bounds = Vec::new(); + for m in methods { + if should_keep_default_for(m, proxy_type)? { + continue; + } + + additional_bounds.extend( m.sig .generics .where_clause @@ -205,8 +217,9 @@ fn gen_header( TypeParamBound::Trait(t) => Some(t), _ => None, } - }) - }); + }), + ); + } // Determine if our proxy type needs a lifetime parameter let (mut params, ty_bounds) = match proxy_type { @@ -219,7 +232,7 @@ fn gen_header( quote! { : #trait_path #relaxation #(+ #additional_bounds)* }, ), ProxyType::Fn | ProxyType::FnMut | ProxyType::FnOnce => { - let fn_bound = gen_fn_type_for_trait(proxy_type, trait_def); + let fn_bound = gen_fn_type_for_trait(proxy_type, trait_def)?; (quote! {}, quote! { : #fn_bound }) } }; @@ -281,9 +294,9 @@ fn gen_header( }; // Combine everything - quote! { + Ok(quote! { impl<#impl_generics> #trait_path for #self_ty #where_clause - } + }) } /// Generates the Fn-trait type (e.g. `FnMut(u32) -> String`) for the given @@ -291,7 +304,10 @@ fn gen_header( /// /// If the trait is unsuitable to be implemented for the given proxy type, an /// error is emitted. -fn gen_fn_type_for_trait(proxy_type: &ProxyType, trait_def: &ItemTrait) -> TokenStream2 { +fn gen_fn_type_for_trait( + proxy_type: &ProxyType, + trait_def: &ItemTrait, +) -> syn::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.first().and_then(|item| { @@ -304,12 +320,11 @@ fn gen_fn_type_for_trait(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Token // If this requirement is not satisfied, we emit an error. if method.is_none() || trait_def.items.len() > 1 { - emit_error!( + return Err(Error::new( trait_def.span(), "this trait cannot be auto-implemented for Fn-traits (only traits with exactly \ one method and no other items are allowed)", - ); - return TokenStream2::new(); + )); } // We checked for `None` above @@ -318,47 +333,53 @@ fn gen_fn_type_for_trait(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Token // Check for forbidden modifier of the method if let Some(const_token) = sig.constness { - emit_error!( + return Err(Error::new( const_token.span(), - "the trait '{}' cannot be auto-implemented for Fn-traits: const methods are not \ + format_args!( + "the trait '{}' cannot be auto-implemented for Fn-traits: const methods are not \ allowed", - trait_def.ident, - ); + trait_def.ident + ), + )); } if let Some(unsafe_token) = &sig.unsafety { - emit_error!( + return Err(Error::new( unsafe_token.span(), - "the trait '{}' cannot be auto-implemented for Fn-traits: unsafe methods are not \ + format_args!( + "the trait '{}' cannot be auto-implemented for Fn-traits: unsafe methods are not \ allowed", - trait_def.ident, - ); + trait_def.ident + ), + )); } if let Some(abi_token) = &sig.abi { - emit_error!( + return Err(Error::new( abi_token.span(), - "the trait '{}' cannot be implemented for Fn-traits: custom ABIs are not allowed", - trait_def.ident, - ); + format_args!( + "the trait '{}' cannot be implemented for Fn-traits: custom ABIs are not allowed", + trait_def.ident + ), + )); } // Function traits cannot support generics in their arguments // These would require HRTB for types instead of just lifetimes for type_param in sig.generics.type_params() { - emit_error!( + return Err(Error::new( type_param.span(), - "the trait '{}' cannot be implemented for Fn-traits: generic arguments are not allowed", - trait_def.ident, - ); + format_args!("the trait '{}' cannot be implemented for Fn-traits: generic arguments are not allowed", + trait_def.ident), + )); } for const_param in sig.generics.const_params() { - emit_error!( + return Err(Error::new( const_param.span(), - "the trait '{}' cannot be implemented for Fn-traits: constant arguments are not allowed", - trait_def.ident, - ); + format_args!("the trait '{}' cannot be implemented for Fn-traits: constant arguments are not allowed", + trait_def.ident), + )); } // ======================================================================= @@ -389,17 +410,14 @@ fn gen_fn_type_for_trait(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Token }; if let Some((fn_traits, receiver, allowed)) = err { - emit_error!( + return Err(Error::new( method.sig.span(), - "the trait '{}' cannot be auto-implemented for {}, because this method has \ + format_args!( + "the trait '{}' cannot be auto-implemented for {}, because this method has \ {} receiver{}", - trait_def.ident, - fn_traits, - receiver, - allowed, - ); - - return TokenStream2::new(); + trait_def.ident, fn_traits, receiver, allowed, + ), + )); } // ======================================================================= @@ -446,17 +464,17 @@ fn gen_fn_type_for_trait(proxy_type: &ProxyType, trait_def: &ItemTrait) -> Token // We skipped the receiver already. FnArg::Receiver(r) => { - abort!( + return Err(Error::new( r.span(), "receiver argument that's not the first argument (auto_impl is confused)", - ); + )); } } } - quote! { + Ok(quote! { for< #(#local_lifetimes),* > #fn_name (#arg_types) #ret - } + }) } /// Generates the implementation of all items of the given trait. These @@ -465,50 +483,43 @@ fn gen_items( proxy_type: &ProxyType, trait_def: &ItemTrait, proxy_ty_param: &Ident, -) -> Vec { +) -> syn::Result> { trait_def .items .iter() - .filter_map(|item| { + .map(|item| { match item { - TraitItem::Const(c) => { - gen_const_item(proxy_type, c, trait_def, proxy_ty_param).ok() - } + TraitItem::Const(c) => gen_const_item(proxy_type, c, trait_def, proxy_ty_param), TraitItem::Fn(method) => { - gen_method_item(proxy_type, method, trait_def, proxy_ty_param).ok() - } - TraitItem::Type(ty) => { - gen_type_item(proxy_type, ty, trait_def, proxy_ty_param).ok() + gen_method_item(proxy_type, method, trait_def, proxy_ty_param) } + TraitItem::Type(ty) => gen_type_item(proxy_type, ty, trait_def, proxy_ty_param), 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. - emit_error!( + Err(Error::new( mac.span(), "traits with macro invocations in their bodies are not \ supported by auto_impl", - ); - None + )) } 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. - emit_error!( + Err(Error::new( v.span(), "unexpected 'verbatim'-item (auto-impl doesn't know how to handle it)", - ); - None + )) } _ => { // `syn` enums are `non_exhaustive` to be future-proof. If a // trait contains a kind of item we don't even know about, we // emit an error. - emit_error!( + Err(Error::new( item.span(), "unknown trait item (auto-impl doesn't know how to handle it)", - ); - None + )) } } }) @@ -525,18 +536,15 @@ fn gen_const_item( item: &TraitItemConst, trait_def: &ItemTrait, proxy_ty_param: &Ident, -) -> Result { +) -> syn::Result { // A trait with associated consts cannot be implemented for Fn* types. if proxy_type.is_fn() { - emit_error!( + return Err(Error::new( item.span(), - "the trait `{}` cannot be auto-implemented for Fn-traits, because it has \ - associated consts", - trait_def.ident; - - note = "only traits with a single method can be implemented for Fn-traits"; - ); - return Err(()); + format_args!("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) + )); } // We simply use the associated const from our type parameter. @@ -559,18 +567,15 @@ fn gen_type_item( item: &TraitItemType, trait_def: &ItemTrait, proxy_ty_param: &Ident, -) -> Result { +) -> syn::Result { // A trait with associated types cannot be implemented for Fn* types. if proxy_type.is_fn() { - emit_error!( + return Err(Error::new( item.span(), - "the trait `{}` cannot be auto-implemented for Fn-traits, because it has \ - associated types", - trait_def.ident; - - note = "only traits with a single method can be implemented for Fn-traits"; - ); - return Err(()); + format_args!("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) + )); } // We simply use the associated type from our type parameter. @@ -594,20 +599,21 @@ fn gen_method_item( item: &TraitItemFn, trait_def: &ItemTrait, proxy_ty_param: &Ident, -) -> Result { +) -> syn::Result { // If this method has a `#[auto_impl(keep_default_for(...))]` attribute for // the given proxy type, we don't generate anything for this impl block. - if should_keep_default_for(item, proxy_type) { + if should_keep_default_for(item, proxy_type)? { return if item.default.is_some() { Ok(TokenStream2::new()) } else { - emit_error!( + Err(Error::new( item.sig.span(), - "the method `{}` has the attribute `keep_default_for` but is not a default \ + format_args!( + "the method `{}` has the attribute `keep_default_for` but is not a default \ method (no body is provided)", - item.sig.ident, - ); - Err(()) + item.sig.ident + ), + )) }; } @@ -617,10 +623,10 @@ fn gen_method_item( let attrs = filter_attrs(&item.attrs); // Check self type and proxy type combination - check_receiver_compatible(proxy_type, self_arg, &trait_def.ident, sig.span()); + check_receiver_compatible(proxy_type, self_arg, &trait_def.ident, sig.span())?; // Generate the list of argument used to call the method. - let (inputs, args) = get_arg_list(sig.inputs.iter()); + let (inputs, args) = get_arg_list(sig.inputs.iter())?; // Construct a signature we'll use to generate the proxy method impl // This is _almost_ the same as the original, except we use the inputs constructed @@ -757,29 +763,25 @@ fn check_receiver_compatible( self_arg: SelfType, trait_name: &Ident, sig_span: Span2, -) { +) -> syn::Result<()> { match (proxy_type, self_arg) { (ProxyType::Ref, SelfType::Mut) | (ProxyType::Ref, SelfType::Value) => { - emit_error!( + Err(Error::new( sig_span, - "the trait `{}` cannot be auto-implemented for immutable references, because \ - this method has a `{}` receiver", + format_args!("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(); - - note = "only `&self` and no receiver are allowed"; - ); + self_arg.as_str().unwrap()), + )) } (ProxyType::RefMut, SelfType::Value) => { - emit_error!( + Err(Error::new( sig_span, - "the trait `{}` cannot be auto-implemented for mutable references, because \ - this method has a `self` receiver", - trait_name; - - note = "only `&self`, `&mut self` and no receiver are allowed"; - ); + format_args!("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,) + )) } (ProxyType::Rc, SelfType::Mut) @@ -792,24 +794,23 @@ fn check_receiver_compatible( "Arc" }; - emit_error!( + Err(Error::new( sig_span, - "the trait `{}` cannot be auto-implemented for {}, because \ - this method has a `{}` receiver", + format_args!("the trait `{}` cannot be auto-implemented for {}, because \ + this method has a `{}` receiver (only `&self` and no receiver are allowed)", trait_name, ptr_name, - self_arg.as_str().unwrap(); - - note = "only `&self` and no receiver are allowed"; - ); + self_arg.as_str().unwrap()) + )) } (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(()) } - _ => {} // All other combinations are fine + _ => Ok(()) // All other combinations are fine } } @@ -818,10 +819,11 @@ fn check_receiver_compatible( /// to an error being emitted. `self` parameters are ignored. fn get_arg_list<'a>( original_inputs: impl Iterator, -) -> (Punctuated, TokenStream2) { +) -> syn::Result<(Punctuated, TokenStream2)> { let mut args = TokenStream2::new(); let mut inputs = Punctuated::new(); + let mut r: Result<(), Error> = Ok(()); for arg in original_inputs { match arg { FnArg::Typed(arg) => { @@ -853,10 +855,16 @@ fn get_arg_list<'a>( ty: arg.ty.clone(), })) } else { - emit_error!( - arg.pat.span(), "argument patterns are not supported by #[auto-impl]"; - help = "please use a simple name like \"foo\" (but not `_`)"; + let err = Error::new( + arg.pat.span(), "argument patterns are not supported by #[auto-impl]; use a simple name like \"foo\" (but not `_`)" ); + + if let Err(ref mut current_err) = r { + current_err.combine(err); + } else { + r = Err(err); + } + continue; } } @@ -869,24 +877,24 @@ fn get_arg_list<'a>( } } - (inputs, args) + r.map(|_| (inputs, args)) } /// Checks if the given method has the attribute `#[auto_impl(keep_default_for(...))]` /// and if it contains the given proxy type. -fn should_keep_default_for(m: &TraitItemFn, proxy_type: &ProxyType) -> bool { +fn should_keep_default_for(m: &TraitItemFn, proxy_type: &ProxyType) -> syn::Result { // Get an iterator of just the attribute we are interested in. let mut it = m .attrs .iter() .filter(|attr| is_our_attr(attr)) - .filter_map(|attr| parse_our_attr(attr).ok()); + .map(|attr| parse_our_attr(attr)); // Check the first (and hopefully only) `keep_default_for` attribute. let out = match it.next() { Some(attr) => { // Check if the attribute lists the given proxy type. - let OurAttr::KeepDefaultFor(proxy_types) = attr; + let OurAttr::KeepDefaultFor(proxy_types) = attr?; proxy_types.contains(proxy_type) } @@ -896,13 +904,13 @@ fn should_keep_default_for(m: &TraitItemFn, proxy_type: &ProxyType) -> bool { // Check if there is another such attribute (which we disallow) if it.next().is_some() { - emit_error!( + return Err(Error::new( m.sig.span(), "found two `keep_default_for` attributes on one method", - ); + )); } - out + Ok(out) } fn filter_attrs(attrs: &[Attribute]) -> Vec { diff --git a/src/lib.rs b/src/lib.rs index 1339113..e8603a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -208,9 +208,6 @@ extern crate proc_macro; extern crate quote; use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use proc_macro_error::{abort_call_site, proc_macro_error, set_dummy}; -use quote::ToTokens; mod analyze; mod attr; @@ -218,44 +215,29 @@ mod gen; mod proxy; /// See crate documentation for more information. -#[proc_macro_error] #[proc_macro_attribute] pub fn auto_impl(args: TokenStream, input: TokenStream) -> TokenStream { - // Make sure that we emit a dummy in case of error - let input: TokenStream2 = input.into(); - set_dummy(input.clone()); + match auto_impl2(args, input.into()) { + Ok(tokens) => tokens.into(), + Err(e) => e.into_compile_error().into(), + } +} +fn auto_impl2( + args: TokenStream, + input: proc_macro2::TokenStream, +) -> syn::Result { // Try to parse the token stream from the attribute to get a list of proxy // types. let proxy_types = proxy::parse_types(args); - match syn::parse2::(input) { - // 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(mut trait_def) => { - let generated = gen::gen_impls(&proxy_types, &trait_def); + let mut trait_def = syn::parse2::(input)?; - // Before returning the trait definition, we have to remove all - // `#[auto_impl(...)]` attributes on all methods. - attr::remove_our_attrs(&mut trait_def); + let generated = gen::gen_impls(&proxy_types, &trait_def)?; - // We emit modified input instead of the original one - // since it's better to remove our attributes even in case of error - set_dummy(trait_def.to_token_stream()); + // Before returning the trait definition, we have to remove all + // `#[auto_impl(...)]` attributes on all methods. + attr::remove_our_attrs(&mut trait_def)?; - quote!(#trait_def #generated).into() - } - - // 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) => abort_call_site!( - "couldn't parse trait item"; - note = e; - note = "the #[auto_impl] attribute can only be applied to traits!"; - ), - } + Ok(quote!(#trait_def #generated)) } diff --git a/src/proxy.rs b/src/proxy.rs index b885791..a7a9823 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -1,5 +1,5 @@ -use proc_macro_error::emit_error; use std::iter::Peekable; +use syn::Error; use crate::proc_macro::{token_stream, TokenStream, TokenTree}; @@ -58,7 +58,7 @@ pub(crate) fn parse_types(args: TokenStream) -> Vec { /// Parses one `ProxyType` from the given token iterator. The iterator must not /// be empty! -fn eat_type(iter: &mut Peekable) -> Result { +fn eat_type(iter: &mut Peekable) -> syn::Result { #[rustfmt::skip] const NOTE_TEXT: &str = "\ attribute format should be `#[auto_impl()]` where `` is \ @@ -71,35 +71,29 @@ fn eat_type(iter: &mut Peekable) -> Result { - emit_error!( - group.span(), - "unexpected group, {}", EXPECTED_TEXT; - note = NOTE_TEXT; - ); - - return Err(()); + return Err(Error::new( + group.span().into(), + format_args!("unexpected group, {}\n{}", EXPECTED_TEXT, NOTE_TEXT), + )); } TokenTree::Literal(lit) => { - emit_error!( - lit.span(), - "unexpected literal, {}", EXPECTED_TEXT; - note = NOTE_TEXT; - ); - - return Err(()); + return Err(Error::new( + lit.span().into(), + format_args!("unexpected literal, {}\n{}", EXPECTED_TEXT, NOTE_TEXT), + )); } TokenTree::Punct(punct) => { // Only '&' are allowed. Everything else leads to an error. if punct.as_char() != '&' { - emit_error!( - punct.span(), - "unexpected punctuation '{}', {}", punct, EXPECTED_TEXT; - note = NOTE_TEXT; - ); - - return Err(()); + return Err(Error::new( + punct.span().into(), + format_args!( + "unexpected punctuation '{}', {}\n{}", + punct, EXPECTED_TEXT, NOTE_TEXT + ), + )); } // Check if the next token is `mut`. If not, we will ignore it. @@ -123,12 +117,10 @@ fn eat_type(iter: &mut Peekable) -> Result ProxyType::FnMut, "FnOnce" => ProxyType::FnOnce, _ => { - emit_error!( - ident.span(), - "unexpected '{}', {}", ident, EXPECTED_TEXT; - note = NOTE_TEXT; - ); - return Err(()); + return Err(Error::new( + ident.span().into(), + format_args!("unexpected '{}', {}\n{}", ident, EXPECTED_TEXT, NOTE_TEXT), + )); } }, }; diff --git a/tests/compile-fail/attr_on_enum.stderr b/tests/compile-fail/attr_on_enum.stderr index 404c867..11874b4 100644 --- a/tests/compile-fail/attr_on_enum.stderr +++ b/tests/compile-fail/attr_on_enum.stderr @@ -1,9 +1,5 @@ -error: couldn't parse trait item - --> tests/compile-fail/attr_on_enum.rs:4:1 +error: expected `trait` + --> tests/compile-fail/attr_on_enum.rs:5:1 | -4 | #[auto_impl(&, &mut)] - | ^^^^^^^^^^^^^^^^^^^^^ - | - = note: expected `trait` - = note: the #[auto_impl] attribute can only be applied to traits! - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | enum Foo { + | ^^^^ diff --git a/tests/compile-fail/attr_on_fn.stderr b/tests/compile-fail/attr_on_fn.stderr index a10645e..472575f 100644 --- a/tests/compile-fail/attr_on_fn.stderr +++ b/tests/compile-fail/attr_on_fn.stderr @@ -1,9 +1,5 @@ -error: couldn't parse trait item - --> tests/compile-fail/attr_on_fn.rs:4:1 +error: expected `trait` + --> tests/compile-fail/attr_on_fn.rs:5:1 | -4 | #[auto_impl(&, &mut)] - | ^^^^^^^^^^^^^^^^^^^^^ - | - = note: expected `trait` - = note: the #[auto_impl] attribute can only be applied to traits! - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | fn foo(s: String) -> u32 { + | ^^ diff --git a/tests/compile-fail/attr_on_impl_block.stderr b/tests/compile-fail/attr_on_impl_block.stderr index 74f67bd..25585fa 100644 --- a/tests/compile-fail/attr_on_impl_block.stderr +++ b/tests/compile-fail/attr_on_impl_block.stderr @@ -1,9 +1,5 @@ -error: couldn't parse trait item - --> tests/compile-fail/attr_on_impl_block.rs:6:1 +error: expected `trait` + --> tests/compile-fail/attr_on_impl_block.rs:7:1 | -6 | #[auto_impl(&, &mut)] - | ^^^^^^^^^^^^^^^^^^^^^ - | - = note: expected `trait` - = note: the #[auto_impl] attribute can only be applied to traits! - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) +7 | impl Foo for usize {} + | ^^^^ diff --git a/tests/compile-fail/attr_on_struct.stderr b/tests/compile-fail/attr_on_struct.stderr index b893ea5..a5ad766 100644 --- a/tests/compile-fail/attr_on_struct.stderr +++ b/tests/compile-fail/attr_on_struct.stderr @@ -1,9 +1,5 @@ -error: couldn't parse trait item - --> tests/compile-fail/attr_on_struct.rs:4:1 +error: expected `trait` + --> tests/compile-fail/attr_on_struct.rs:5:1 | -4 | #[auto_impl(&, &mut)] - | ^^^^^^^^^^^^^^^^^^^^^ - | - = note: expected `trait` - = note: the #[auto_impl] attribute can only be applied to traits! - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | struct Foo { + | ^^^^^^ diff --git a/tests/compile-fail/attr_on_type.stderr b/tests/compile-fail/attr_on_type.stderr index b89b58b..11d7fb3 100644 --- a/tests/compile-fail/attr_on_type.stderr +++ b/tests/compile-fail/attr_on_type.stderr @@ -1,9 +1,5 @@ -error: couldn't parse trait item - --> tests/compile-fail/attr_on_type.rs:4:1 +error: expected `trait` + --> tests/compile-fail/attr_on_type.rs:5:1 | -4 | #[auto_impl(&, &mut)] - | ^^^^^^^^^^^^^^^^^^^^^ - | - = note: expected `trait` - = note: the #[auto_impl] attribute can only be applied to traits! - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | type Baz = String; + | ^^^^ diff --git a/tests/compile-fail/attr_on_unit_struct.stderr b/tests/compile-fail/attr_on_unit_struct.stderr index 36516cf..fd188f1 100644 --- a/tests/compile-fail/attr_on_unit_struct.stderr +++ b/tests/compile-fail/attr_on_unit_struct.stderr @@ -1,9 +1,5 @@ -error: couldn't parse trait item - --> tests/compile-fail/attr_on_unit_struct.rs:4:1 +error: expected `trait` + --> tests/compile-fail/attr_on_unit_struct.rs:5:1 | -4 | #[auto_impl(&, &mut)] - | ^^^^^^^^^^^^^^^^^^^^^ - | - = note: expected `trait` - = note: the #[auto_impl] attribute can only be applied to traits! - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) +5 | struct Foo(usize, String); + | ^^^^^^ diff --git a/tests/compile-fail/fn_associated_const.stderr b/tests/compile-fail/fn_associated_const.stderr index ca547df..dd97607 100644 --- a/tests/compile-fail/fn_associated_const.stderr +++ b/tests/compile-fail/fn_associated_const.stderr @@ -7,30 +7,3 @@ error: this trait cannot be auto-implemented for Fn-traits (only traits with exa 8 | | fn a(&self); 9 | | } | |_^ - -error: the trait `Foo` cannot be auto-implemented for Fn-traits, because it has associated consts - --> tests/compile-fail/fn_associated_const.rs:6:5 - | -6 | const LEN: usize; - | ^^^^^^^^^^^^^^^^^ - | - = note: only traits with a single method can be implemented for Fn-traits - -error[E0046]: not all trait items implemented, missing: `LEN` - --> tests/compile-fail/fn_associated_const.rs:4:1 - | -4 | #[auto_impl(Fn)] - | ^^^^^^^^^^^^^^^^ missing `LEN` in implementation -5 | trait Foo { -6 | const LEN: usize; - | ---------------- `LEN` from trait - | - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0618]: expected function, found `&T` - --> tests/compile-fail/fn_associated_const.rs:4:1 - | -4 | #[auto_impl(Fn)] - | ^^^^^^^^^^^^^^^^ call expression requires function - | - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/compile-fail/fn_associated_type.stderr b/tests/compile-fail/fn_associated_type.stderr index 21ea760..e96fe00 100644 --- a/tests/compile-fail/fn_associated_type.stderr +++ b/tests/compile-fail/fn_associated_type.stderr @@ -7,30 +7,3 @@ error: this trait cannot be auto-implemented for Fn-traits (only traits with exa 8 | | fn a(&self); 9 | | } | |_^ - -error: the trait `Foo` cannot be auto-implemented for Fn-traits, because it has associated types - --> tests/compile-fail/fn_associated_type.rs:6:5 - | -6 | type Out; - | ^^^^^^^^^ - | - = note: only traits with a single method can be implemented for Fn-traits - -error[E0046]: not all trait items implemented, missing: `Out` - --> tests/compile-fail/fn_associated_type.rs:4:1 - | -4 | #[auto_impl(Fn)] - | ^^^^^^^^^^^^^^^^ missing `Out` in implementation -5 | trait Foo { -6 | type Out; - | -------- `Out` from trait - | - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0618]: expected function, found `&T` - --> tests/compile-fail/fn_associated_type.rs:4:1 - | -4 | #[auto_impl(Fn)] - | ^^^^^^^^^^^^^^^^ call expression requires function - | - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/compile-fail/fn_generics.stderr b/tests/compile-fail/fn_generics.stderr index 4a29b66..04221a5 100644 --- a/tests/compile-fail/fn_generics.stderr +++ b/tests/compile-fail/fn_generics.stderr @@ -4,11 +4,10 @@ error: the trait 'Greeter' cannot be implemented for Fn-traits: generic argument 7 | fn greet(&self, name: T); | ^^^^^^^^^^ -error[E0412]: cannot find type `T` in this scope - --> tests/compile-fail/fn_generics.rs:7:39 +warning: unused import: `std::fmt::Display` + --> tests/compile-fail/fn_generics.rs:1:5 | -5 | #[auto_impl(Fn)] - | ---------------- similarly named type parameter `U` defined here -6 | trait Greeter { -7 | fn greet(&self, name: T); - | ^ help: a type parameter with a similar name exists: `U` +1 | use std::fmt::Display; + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(unused_imports)]` on by default diff --git a/tests/compile-fail/fn_multiple_methods.stderr b/tests/compile-fail/fn_multiple_methods.stderr index 41f59d3..6364fc6 100644 --- a/tests/compile-fail/fn_multiple_methods.stderr +++ b/tests/compile-fail/fn_multiple_methods.stderr @@ -6,11 +6,3 @@ error: this trait cannot be auto-implemented for Fn-traits (only traits with exa 7 | | fn b(&self); 8 | | } | |_^ - -error[E0618]: expected function, found `&T` - --> tests/compile-fail/fn_multiple_methods.rs:4:1 - | -4 | #[auto_impl(Fn)] - | ^^^^^^^^^^^^^^^^ call expression requires function - | - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/compile-fail/keep_default_for_on_required_method.stderr b/tests/compile-fail/keep_default_for_on_required_method.stderr index 67056ab..afd4512 100644 --- a/tests/compile-fail/keep_default_for_on_required_method.stderr +++ b/tests/compile-fail/keep_default_for_on_required_method.stderr @@ -3,14 +3,3 @@ error: the method `required` has the attribute `keep_default_for` but is not a d | 7 | fn required(&self); | ^^^^^^^^^^^^^^^^^^ - -error[E0046]: not all trait items implemented, missing: `required` - --> tests/compile-fail/keep_default_for_on_required_method.rs:4:1 - | -4 | #[auto_impl(&)] - | ^^^^^^^^^^^^^^^ missing `required` in implementation -... -7 | fn required(&self); - | ------------------- `required` from trait - | - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/compile-fail/mut_self_for_arc.stderr b/tests/compile-fail/mut_self_for_arc.stderr index ce0f44f..89eef53 100644 --- a/tests/compile-fail/mut_self_for_arc.stderr +++ b/tests/compile-fail/mut_self_for_arc.stderr @@ -1,7 +1,5 @@ -error: the trait `Foo` cannot be auto-implemented for Arc, because this method has a `&mut self` receiver +error: the trait `Foo` cannot be auto-implemented for Arc, because this method has a `&mut self` receiver (only `&self` and no receiver are allowed) --> tests/compile-fail/mut_self_for_arc.rs:6:5 | 6 | fn foo(&mut self); | ^^^^^^^^^^^^^^^^^ - | - = note: only `&self` and no receiver are allowed diff --git a/tests/compile-fail/mut_self_for_immutable_ref.stderr b/tests/compile-fail/mut_self_for_immutable_ref.stderr index d05e911..92e038f 100644 --- a/tests/compile-fail/mut_self_for_immutable_ref.stderr +++ b/tests/compile-fail/mut_self_for_immutable_ref.stderr @@ -1,7 +1,5 @@ -error: the trait `Foo` cannot be auto-implemented for immutable references, because this method has a `&mut self` receiver +error: the trait `Foo` cannot be auto-implemented for immutable references, because this method has a `&mut self` receiver (only `&self` and no receiver are allowed) --> tests/compile-fail/mut_self_for_immutable_ref.rs:6:5 | 6 | fn foo(&mut self); | ^^^^^^^^^^^^^^^^^ - | - = note: only `&self` and no receiver are allowed diff --git a/tests/compile-fail/mut_self_for_rc.stderr b/tests/compile-fail/mut_self_for_rc.stderr index 03da8bd..ee0772a 100644 --- a/tests/compile-fail/mut_self_for_rc.stderr +++ b/tests/compile-fail/mut_self_for_rc.stderr @@ -1,7 +1,5 @@ -error: the trait `Foo` cannot be auto-implemented for Rc, because this method has a `&mut self` receiver +error: the trait `Foo` cannot be auto-implemented for Rc, because this method has a `&mut self` receiver (only `&self` and no receiver are allowed) --> tests/compile-fail/mut_self_for_rc.rs:6:5 | 6 | fn foo(&mut self); | ^^^^^^^^^^^^^^^^^ - | - = note: only `&self` and no receiver are allowed diff --git a/tests/compile-fail/value_self_for_immutable_ref.stderr b/tests/compile-fail/value_self_for_immutable_ref.stderr index 5d1fcb2..9d37b9f 100644 --- a/tests/compile-fail/value_self_for_immutable_ref.stderr +++ b/tests/compile-fail/value_self_for_immutable_ref.stderr @@ -1,7 +1,5 @@ -error: the trait `Foo` cannot be auto-implemented for immutable references, because this method has a `self` receiver +error: the trait `Foo` cannot be auto-implemented for immutable references, because this method has a `self` receiver (only `&self` and no receiver are allowed) --> tests/compile-fail/value_self_for_immutable_ref.rs:6:5 | 6 | fn foo(self); | ^^^^^^^^^^^^ - | - = note: only `&self` and no receiver are allowed diff --git a/tests/compile-fail/value_self_for_mutable_ref.stderr b/tests/compile-fail/value_self_for_mutable_ref.stderr index 978f221..71649c5 100644 --- a/tests/compile-fail/value_self_for_mutable_ref.stderr +++ b/tests/compile-fail/value_self_for_mutable_ref.stderr @@ -1,7 +1,5 @@ -error: the trait `Foo` cannot be auto-implemented for mutable references, because this method has a `self` receiver +error: the trait `Foo` cannot be auto-implemented for mutable references, because this method has a `self` receiver (only `&self`, `&mut self` and no receiver are allowed) --> tests/compile-fail/value_self_for_mutable_ref.rs:6:5 | 6 | fn foo(self); | ^^^^^^^^^^^^ - | - = note: only `&self`, `&mut self` and no receiver are allowed