diff --git a/Cargo.toml b/Cargo.toml index ea0f3d4..ed186a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,5 +24,8 @@ tracing-attributes = "0.1.8" tracing-futures = "0.2" trybuild = { version = "1.0.19", features = ["diff"] } +[build-dependencies] +version_check = "0.9" + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..c2697ac --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +fn main() { + if let Some(true) = version_check::is_max_version("1.46") { + println!("cargo:rustc-cfg=self_span_hack"); + } +} diff --git a/src/expand.rs b/src/expand.rs index fb83df1..0b8f4a0 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -1,19 +1,23 @@ -use crate::lifetime::{has_async_lifetime, CollectLifetimes}; +use crate::lifetime::CollectLifetimes; use crate::parse::Item; -use crate::receiver::{ - has_self_in_block, has_self_in_sig, has_self_in_where_predicate, ReplaceReceiver, -}; +use crate::receiver::{ mut_pat, has_self_in_block, has_self_in_sig, ReplaceSelf}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; -use std::mem; use syn::punctuated::Punctuated; +use syn::spanned::Spanned; use syn::visit_mut::VisitMut; use syn::{ parse_quote, Block, FnArg, GenericParam, Generics, Ident, ImplItem, Lifetime, Pat, PatIdent, - Path, Receiver, ReturnType, Signature, Stmt, Token, TraitItem, Type, TypeParam, TypeParamBound, + Receiver, ReturnType, Signature, Stmt, Token, TraitItem, Type, TypeParamBound, WhereClause, }; +macro_rules! parse_quote_spanned { + ($span:expr => $($t:tt)*) => ( + syn::parse2(quote_spanned!($span => $($t)*)).unwrap() + ) +} + impl ToTokens for Item { fn to_tokens(&self, tokens: &mut TokenStream) { match self { @@ -26,14 +30,11 @@ impl ToTokens for Item { #[derive(Clone, Copy)] enum Context<'a> { Trait { - name: &'a Ident, generics: &'a Generics, supertraits: &'a Supertraits, }, Impl { impl_generics: &'a Generics, - receiver: &'a Type, - as_trait: &'a Path, }, } @@ -51,15 +52,33 @@ impl Context<'_> { } }) } + + fn generics_span(&self) -> Span { + match self { + Context::Trait { generics, .. } => generics.span(), + Context::Impl { impl_generics } => impl_generics.span(), + } + } } type Supertraits = Punctuated; pub fn expand(input: &mut Item, is_local: bool) { + let inner_method_attrs = &[ + parse_quote!(#[allow(clippy::used_underscore_binding)]), + parse_quote!(#[allow(clippy::type_repetition_in_bounds)]), + parse_quote!(#[allow(clippy::let_unit_value)]), + ]; + + let trait_method_attrs = &[ + parse_quote!(#[must_use]), + parse_quote!(#[allow(clippy::type_repetition_in_bounds)]), + parse_quote!(#[allow(clippy::let_unit_value)]), + ]; + match input { Item::Trait(input) => { let context = Context::Trait { - name: &input.ident, generics: &input.generics, supertraits: &input.supertraits, }; @@ -71,20 +90,18 @@ pub fn expand(input: &mut Item, is_local: bool) { let mut has_self = has_self_in_sig(sig); if let Some(block) = block { has_self |= has_self_in_block(block); - transform_block(context, sig, block, has_self, is_local); - method - .attrs - .push(parse_quote!(#[allow(clippy::used_underscore_binding)])); + transform_block(sig, block); + method.attrs.extend_from_slice(inner_method_attrs); } let has_default = method.default.is_some(); transform_sig(context, sig, has_self, has_default, is_local); - method.attrs.push(parse_quote!(#[must_use])); + method.attrs.extend_from_slice(trait_method_attrs); } } } } Item::Impl(input) => { - let mut lifetimes = CollectLifetimes::new("'impl"); + let mut lifetimes = CollectLifetimes::new("'impl", input.generics.span()); lifetimes.visit_type_mut(&mut *input.self_ty); lifetimes.visit_path_mut(&mut input.trait_.as_mut().unwrap().1); let params = &input.generics.params; @@ -93,8 +110,6 @@ pub fn expand(input: &mut Item, is_local: bool) { let context = Context::Impl { impl_generics: &input.generics, - receiver: &input.self_ty, - as_trait: &input.trait_.as_ref().unwrap().1, }; for inner in &mut input.items { if let ImplItem::Method(method) = inner { @@ -102,11 +117,9 @@ pub fn expand(input: &mut Item, is_local: bool) { if sig.asyncness.is_some() { let block = &mut method.block; let has_self = has_self_in_sig(sig) || has_self_in_block(block); - transform_block(context, sig, block, has_self, is_local); + transform_block(sig, block); transform_sig(context, sig, has_self, false, is_local); - method - .attrs - .push(parse_quote!(#[allow(clippy::used_underscore_binding)])); + method.attrs.extend_from_slice(inner_method_attrs); } } } @@ -141,7 +154,11 @@ fn transform_sig( ReturnType::Type(_, ret) => quote!(#ret), }; - let mut lifetimes = CollectLifetimes::new("'life"); + let default_span = sig.ident.span() + .join(sig.paren_token.span) + .unwrap_or_else(|| sig.ident.span()); + + let mut lifetimes = CollectLifetimes::new("'life", default_span); for arg in sig.inputs.iter_mut() { match arg { FnArg::Receiver(arg) => lifetimes.visit_receiver_mut(arg), @@ -149,13 +166,6 @@ fn transform_sig( } } - let where_clause = sig - .generics - .where_clause - .get_or_insert_with(|| WhereClause { - where_token: Default::default(), - predicates: Punctuated::new(), - }); for param in sig .generics .params @@ -165,33 +175,41 @@ fn transform_sig( match param { GenericParam::Type(param) => { let param = ¶m.ident; - where_clause + let span = param.span(); + where_clause_or_default(&mut sig.generics.where_clause) .predicates - .push(parse_quote!(#param: 'async_trait)); + .push(parse_quote_spanned!(span => #param: 'async_trait)); } GenericParam::Lifetime(param) => { let param = ¶m.lifetime; - where_clause + let span = param.span(); + where_clause_or_default(&mut sig.generics.where_clause) .predicates - .push(parse_quote!(#param: 'async_trait)); + .push(parse_quote_spanned!(span => #param: 'async_trait)); } GenericParam::Const(_) => {} } } + for elided in lifetimes.elided { - sig.generics.params.push(parse_quote!(#elided)); - where_clause + push_param(&mut sig.generics, parse_quote!(#elided)); + where_clause_or_default(&mut sig.generics.where_clause) .predicates - .push(parse_quote!(#elided: 'async_trait)); + .push(parse_quote_spanned!(elided.span() => #elided: 'async_trait)); } - sig.generics.params.push(parse_quote!('async_trait)); + + push_param(&mut sig.generics, parse_quote_spanned!(default_span => 'async_trait)); + + let first_bound = where_clause_or_default(&mut sig.generics.where_clause).predicates.first(); + let bound_span = first_bound.map_or(default_span, Spanned::span); + if has_self { let bound: Ident = match sig.inputs.iter().next() { Some(FnArg::Receiver(Receiver { reference: Some(_), mutability: None, .. - })) => parse_quote!(Sync), + })) => parse_quote_spanned!(bound_span => Sync), Some(FnArg::Typed(arg)) if match (arg.pat.as_ref(), arg.ty.as_ref()) { (Pat::Ident(pat), Type::Reference(ty)) => { @@ -200,18 +218,21 @@ fn transform_sig( _ => false, } => { - parse_quote!(Sync) + parse_quote_spanned!(bound_span => Sync) } - _ => parse_quote!(Send), + _ => parse_quote_spanned!(bound_span => Send), }; + let assume_bound = match context { Context::Trait { supertraits, .. } => !has_default || has_bound(supertraits, &bound), Context::Impl { .. } => true, }; + + let where_clause = where_clause_or_default(&mut sig.generics.where_clause); where_clause.predicates.push(if assume_bound || is_local { - parse_quote!(Self: 'async_trait) + parse_quote_spanned!(bound_span => Self: 'async_trait) } else { - parse_quote!(Self: ::core::marker::#bound + 'async_trait) + parse_quote_spanned!(bound_span => Self: ::core::marker::#bound + 'async_trait) }); } @@ -226,17 +247,19 @@ fn transform_sig( ident.by_ref = None; ident.mutability = None; } else { - let positional = positional_arg(i); - *arg.pat = parse_quote!(#positional); + let span = arg.pat.span(); + let positional = positional_arg(i, span); + let m = mut_pat(&mut arg.pat); + arg.pat = parse_quote!(#m #positional); } } } } let bounds = if is_local { - quote!('async_trait) + quote_spanned!(context.generics_span() => 'async_trait) } else { - quote!(::core::marker::Send + 'async_trait) + quote_spanned!(context.generics_span() => ::core::marker::Send + 'async_trait) }; sig.output = parse_quote! { @@ -247,21 +270,25 @@ fn transform_sig( } // Input: -// async fn f(&self, x: &T) -> Ret { -// self + x +// async fn f(&self, x: &T, (a, b): (A, B)) -> Ret { +// self + x + a + b // } // // Output: -// async fn f(_self: &AsyncTrait, x: &T) -> Ret { -// _self + x -// } -// Box::pin(async_trait_method::(self, x)) +// Box::pin(async move { +// let ___ret: Ret = { +// let __self = self; +// let x = x; +// let (a, b) = __arg1; +// +// __self + x + a + b +// }; +// +// ___ret +// }) fn transform_block( - context: Context, sig: &mut Signature, block: &mut Block, - has_self: bool, - is_local: bool, ) { if let Some(Stmt::Item(syn::Item::Verbatim(item))) = block.stmts.first() { if block.stmts.len() == 1 && item.to_string() == ";" { @@ -269,225 +296,61 @@ fn transform_block( } } - let inner = format_ident!("__{}", sig.ident); - let args = sig.inputs.iter().enumerate().map(|(i, arg)| match arg { - FnArg::Receiver(Receiver { self_token, .. }) => quote!(#self_token), - FnArg::Typed(arg) => { - if let Pat::Ident(PatIdent { ident, .. }) = &*arg.pat { - quote!(#ident) - } else { - positional_arg(i).into_token_stream() - } - } - }); - - let mut standalone = sig.clone(); - standalone.ident = inner.clone(); - - let generics = match context { - Context::Trait { generics, .. } => generics, - Context::Impl { impl_generics, .. } => impl_generics, - }; - - let mut outer_generics = generics.clone(); - for p in &mut outer_generics.params { - match p { - GenericParam::Type(t) => t.default = None, - GenericParam::Const(c) => c.default = None, - GenericParam::Lifetime(_) => {} - } - } - if !has_self { - if let Some(mut where_clause) = outer_generics.where_clause { - where_clause.predicates = where_clause - .predicates - .into_iter() - .filter_map(|mut pred| { - if has_self_in_where_predicate(&mut pred) { - None - } else { - Some(pred) - } - }) - .collect(); - outer_generics.where_clause = Some(where_clause); - } - } - - let fn_generics = mem::replace(&mut standalone.generics, outer_generics); - standalone.generics.params.extend(fn_generics.params); - if let Some(where_clause) = fn_generics.where_clause { - standalone - .generics - .make_where_clause() - .predicates - .extend(where_clause.predicates); - } - - if has_async_lifetime(&mut standalone, block) { - standalone.generics.params.push(parse_quote!('async_trait)); - } - - let mut types = standalone - .generics - .type_params() - .map(|param| param.ident.clone()) - .collect::>(); - - let mut self_bound = None::; - match standalone.inputs.iter_mut().next() { - Some( - arg @ FnArg::Receiver(Receiver { - reference: Some(_), .. - }), - ) => { - let (lifetime, mutability, self_token) = match arg { - FnArg::Receiver(Receiver { - reference: Some((_, lifetime)), - mutability, - self_token, - .. - }) => (lifetime, mutability, self_token), - _ => unreachable!(), - }; - let under_self = Ident::new("_self", self_token.span); - match context { - Context::Trait { .. } => { - self_bound = Some(match mutability { - Some(_) => parse_quote!(::core::marker::Send), - None => parse_quote!(::core::marker::Sync), - }); - *arg = parse_quote! { - #under_self: &#lifetime #mutability AsyncTrait - }; - } - Context::Impl { receiver, .. } => { - let mut ty = quote!(#receiver); - if let Type::TraitObject(trait_object) = receiver { - if trait_object.dyn_token.is_none() { - ty = quote!(dyn #ty); - } - if trait_object.bounds.len() > 1 { - ty = quote!((#ty)); - } - } - *arg = parse_quote! { - #under_self: &#lifetime #mutability #ty - }; - } - } - } - Some(arg @ FnArg::Receiver(_)) => { - let (self_token, mutability) = match arg { - FnArg::Receiver(Receiver { - self_token, - mutability, - .. - }) => (self_token, mutability), - _ => unreachable!(), - }; - let under_self = Ident::new("_self", self_token.span); - match context { - Context::Trait { .. } => { - self_bound = Some(parse_quote!(::core::marker::Send)); - *arg = parse_quote! { - #mutability #under_self: AsyncTrait - }; - } - Context::Impl { receiver, .. } => { - *arg = parse_quote! { - #mutability #under_self: #receiver - }; - } - } + let self_prefix = "__"; + let mut self_span = None; + let decls = sig.inputs.iter().enumerate().map(|(i, arg)| match arg { + FnArg::Receiver(Receiver { self_token, mutability, .. }) => { + let mut ident = format_ident!("{}self", self_prefix); + ident.set_span(self_token.span()); + self_span = Some(self_token.span()); + quote!(let #mutability #ident = #self_token;) } - Some(FnArg::Typed(arg)) => { - if let Pat::Ident(arg) = &mut *arg.pat { - if arg.ident == "self" { - arg.ident = Ident::new("_self", arg.ident.span()); + FnArg::Typed(arg) => { + if let Pat::Ident(PatIdent { ident, mutability, .. }) = &*arg.pat { + if ident == "self" { + self_span = Some(ident.span()); + let prefixed = format_ident!("{}{}", self_prefix, ident); + quote!(let #mutability #prefixed = #ident;) + } else { + quote!(let #mutability #ident = #ident;) } + } else { + let pat = &arg.pat; + let ident = positional_arg(i, pat.span()); + quote!(let #pat = #ident;) } } - _ => {} - } - - if let Context::Trait { name, generics, .. } = context { - if has_self { - let (_, generics, _) = generics.split_for_impl(); - let mut self_param: TypeParam = parse_quote!(AsyncTrait: ?Sized + #name #generics); - if !is_local { - self_param.bounds.extend(self_bound); - } - let count = standalone - .generics - .params - .iter() - .take_while(|param| { - if let GenericParam::Const(_) = param { - false - } else { - true - } - }) - .count(); - standalone - .generics - .params - .insert(count, GenericParam::Type(self_param)); - types.push(Ident::new("Self", Span::call_site())); - } - } + }).collect::>(); - if let Some(where_clause) = &mut standalone.generics.where_clause { - // Work around an input bound like `where Self::Output: Send` expanding - // to `where ::Output: Send` which is illegal syntax because - // `where` is reserved for future use... :( - where_clause.predicates.insert(0, parse_quote!((): Sized)); + if let Some(span) = self_span { + let mut replace_self = ReplaceSelf(self_prefix, span); + replace_self.visit_block_mut(block); } - let mut replace = match context { - Context::Trait { .. } => ReplaceReceiver::with(parse_quote!(AsyncTrait)), - Context::Impl { - receiver, as_trait, .. - } => ReplaceReceiver::with_as_trait(receiver.clone(), as_trait.clone()), + let stmts = &block.stmts; + let ret_ty = match &sig.output { + ReturnType::Default => quote_spanned!(block.span()=>()), + ReturnType::Type(_, ret) => quote!(#ret), }; - replace.visit_signature_mut(&mut standalone); - replace.visit_block_mut(block); - let mut generics = types; - let consts = standalone - .generics - .const_params() - .map(|param| param.ident.clone()); - generics.extend(consts); + let box_pin = quote_spanned!(ret_ty.span()=> + Box::pin(async move { + let __ret: #ret_ty = { + #(#decls)* + let __async_trait: (); + #(#stmts)* + }; - let allow_non_snake_case = if sig.ident != sig.ident.to_string().to_lowercase() { - Some(quote!(non_snake_case,)) - } else { - None - }; + #[allow(unreachable_code)] + __ret + }) + ); - let brace = block.brace_token; - let box_pin = quote_spanned!(brace.span=> { - #[allow( - #allow_non_snake_case - unused_parens, // https://github.com/dtolnay/async-trait/issues/118 - clippy::missing_docs_in_private_items, - clippy::needless_lifetimes, - clippy::ptr_arg, - clippy::trivially_copy_pass_by_ref, - clippy::type_repetition_in_bounds, - clippy::used_underscore_binding, - )] - #standalone #block - Box::pin(#inner::<#(#generics),*>(#(#args),*)) - }); - *block = parse_quote!(#box_pin); - block.brace_token = brace; + block.stmts = parse_quote!(#box_pin); } -fn positional_arg(i: usize) -> Ident { - format_ident!("__arg{}", i) +fn positional_arg(i: usize, span: Span) -> Ident { + format_ident!("__arg{}", i, span = span) } fn has_bound(supertraits: &Supertraits, marker: &Ident) -> bool { @@ -500,3 +363,20 @@ fn has_bound(supertraits: &Supertraits, marker: &Ident) -> bool { } false } + +fn where_clause_or_default(clause: &mut Option) -> &mut WhereClause { + clause.get_or_insert_with(|| WhereClause { + where_token: Default::default(), + predicates: Punctuated::new(), + }) +} + +fn push_param(generics: &mut Generics, param: GenericParam) { + let span = param.span(); + if generics.params.is_empty() { + generics.lt_token = parse_quote_spanned!(span => <); + generics.gt_token = parse_quote_spanned!(span => >); + } + + generics.params.push(param); +} diff --git a/src/lib.rs b/src/lib.rs index 6769e2d..1efa538 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -321,7 +321,6 @@ mod expand; mod lifetime; mod parse; mod receiver; -mod respan; use crate::args::Args; use crate::expand::expand; diff --git a/src/lifetime.rs b/src/lifetime.rs index 9d2066b..c28c37a 100644 --- a/src/lifetime.rs +++ b/src/lifetime.rs @@ -1,59 +1,44 @@ use proc_macro2::Span; +use syn::spanned::Spanned; use syn::visit_mut::{self, VisitMut}; -use syn::{Block, GenericArgument, Item, Lifetime, Receiver, Signature, TypeReference}; - -pub fn has_async_lifetime(sig: &mut Signature, block: &mut Block) -> bool { - let mut visitor = HasAsyncLifetime(false); - visitor.visit_signature_mut(sig); - visitor.visit_block_mut(block); - visitor.0 -} - -struct HasAsyncLifetime(bool); - -impl VisitMut for HasAsyncLifetime { - fn visit_lifetime_mut(&mut self, life: &mut Lifetime) { - self.0 |= life.to_string() == "'async_trait"; - } - - fn visit_item_mut(&mut self, _: &mut Item) { - // Do not recurse into nested items. - } -} +use syn::{GenericArgument, Lifetime, Receiver, TypeReference}; pub struct CollectLifetimes { pub elided: Vec, pub explicit: Vec, pub name: &'static str, + pub default_span: Span, } impl CollectLifetimes { - pub fn new(name: &'static str) -> Self { + pub fn new(name: &'static str, default_span: Span) -> Self { CollectLifetimes { elided: Vec::new(), explicit: Vec::new(), name, + default_span, } } fn visit_opt_lifetime(&mut self, lifetime: &mut Option) { match lifetime { - None => *lifetime = Some(self.next_lifetime()), + None => *lifetime = Some(self.next_lifetime(None)), Some(lifetime) => self.visit_lifetime(lifetime), } } fn visit_lifetime(&mut self, lifetime: &mut Lifetime) { if lifetime.ident == "_" { - *lifetime = self.next_lifetime(); + *lifetime = self.next_lifetime(lifetime.span()); } else { self.explicit.push(lifetime.clone()); } } - fn next_lifetime(&mut self) -> Lifetime { + fn next_lifetime>>(&mut self, span: S) -> Lifetime { let name = format!("{}{}", self.name, self.elided.len()); - let life = Lifetime::new(&name, Span::call_site()); + let span = span.into().unwrap_or(self.default_span); + let life = Lifetime::new(&name, span); self.elided.push(life.clone()); life } diff --git a/src/receiver.rs b/src/receiver.rs index 4273359..b3524cd 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -1,14 +1,8 @@ -use crate::respan::respan; -use proc_macro2::{Group, Spacing, Span, TokenStream, TokenTree}; -use quote::{quote, quote_spanned}; -use std::iter::FromIterator; -use std::mem; -use syn::punctuated::Punctuated; +use proc_macro2::{Group, Span, TokenStream, TokenTree}; use syn::visit_mut::{self, VisitMut}; use syn::{ - parse_quote, Block, Error, ExprPath, ExprStruct, Ident, Item, Macro, PatPath, PatStruct, - PatTupleStruct, Path, PathArguments, QSelf, Receiver, Signature, Token, Type, TypePath, - WherePredicate, + Block, ExprPath, Ident, Item, Macro, PatPath, PatIdent, Pat, Receiver, + Signature, Token, TypePath, }; pub fn has_self_in_sig(sig: &mut Signature) -> bool { @@ -17,12 +11,6 @@ pub fn has_self_in_sig(sig: &mut Signature) -> bool { visitor.0 } -pub fn has_self_in_where_predicate(where_predicate: &mut WherePredicate) -> bool { - let mut visitor = HasSelf(false); - visitor.visit_where_predicate_mut(where_predicate); - visitor.0 -} - pub fn has_self_in_block(block: &mut Block) -> bool { let mut visitor = HasSelf(false); visitor.visit_block_mut(block); @@ -37,6 +25,32 @@ fn has_self_in_token_stream(tokens: TokenStream) -> bool { }) } +pub fn mut_pat(pat: &mut Pat) -> Option { + let mut visitor = HasMutPat(None); + visitor.visit_pat_mut(pat); + visitor.0 +} + +fn contains_fn(tokens: TokenStream) -> bool { + tokens.into_iter().any(|tt| match tt { + TokenTree::Ident(ident) => ident == "fn", + TokenTree::Group(group) => contains_fn(group.stream()), + _ => false, + }) +} + +struct HasMutPat(Option); + +impl VisitMut for HasMutPat { + fn visit_pat_ident_mut(&mut self, i: &mut PatIdent) { + if let Some(m) = i.mutability { + self.0 = Some(m); + } else { + visit_mut::visit_pat_ident_mut(self, i); + } + } +} + struct HasSelf(bool); impl VisitMut for HasSelf { @@ -70,252 +84,49 @@ impl VisitMut for HasSelf { } } -pub struct ReplaceReceiver { - pub with: Type, - pub as_trait: Option, -} - -impl ReplaceReceiver { - pub fn with(ty: Type) -> Self { - ReplaceReceiver { - with: ty, - as_trait: None, - } - } - - pub fn with_as_trait(ty: Type, as_trait: Path) -> Self { - ReplaceReceiver { - with: ty, - as_trait: Some(as_trait), - } - } - - fn self_ty(&self, span: Span) -> Type { - respan(&self.with, span) - } - - fn self_to_qself_type(&self, qself: &mut Option, path: &mut Path) { - let include_as_trait = true; - self.self_to_qself(qself, path, include_as_trait); - } - - fn self_to_qself_expr(&self, qself: &mut Option, path: &mut Path) { - let include_as_trait = false; - self.self_to_qself(qself, path, include_as_trait); - } - - fn self_to_qself(&self, qself: &mut Option, path: &mut Path, include_as_trait: bool) { - if path.leading_colon.is_some() { - return; - } - - let first = &path.segments[0]; - if first.ident != "Self" || !first.arguments.is_empty() { - return; - } - - if path.segments.len() == 1 { - self.self_to_expr_path(path); - return; - } - - let span = first.ident.span(); - *qself = Some(QSelf { - lt_token: Token![<](span), - ty: Box::new(self.self_ty(span)), - position: 0, - as_token: None, - gt_token: Token![>](span), - }); - - if include_as_trait && self.as_trait.is_some() { - let as_trait = self.as_trait.as_ref().unwrap().clone(); - path.leading_colon = as_trait.leading_colon; - qself.as_mut().unwrap().position = as_trait.segments.len(); - - let segments = mem::replace(&mut path.segments, as_trait.segments); - path.segments.push_punct(Default::default()); - path.segments.extend(segments.into_pairs().skip(1)); - } else { - path.leading_colon = Some(**path.segments.pairs().next().unwrap().punct().unwrap()); - - let segments = mem::replace(&mut path.segments, Punctuated::new()); - path.segments = segments.into_pairs().skip(1).collect(); - } - } +pub struct ReplaceSelf<'a>(pub &'a str, pub Span); - fn self_to_expr_path(&self, path: &mut Path) { - if path.leading_colon.is_some() { - return; - } - - let first = &path.segments[0]; - if first.ident != "Self" || !first.arguments.is_empty() { - return; - } - - if let Type::Path(self_ty) = self.self_ty(first.ident.span()) { - let variant = mem::replace(path, self_ty.path); - for segment in &mut path.segments { - if let PathArguments::AngleBracketed(bracketed) = &mut segment.arguments { - if bracketed.colon2_token.is_none() && !bracketed.args.is_empty() { - bracketed.colon2_token = Some(Default::default()); - } - } - } - if variant.segments.len() > 1 { - path.segments.push_punct(Default::default()); - path.segments.extend(variant.segments.into_pairs().skip(1)); +impl ReplaceSelf<'_> { + fn visit_token_stream(&mut self, tt: TokenStream) -> TokenStream { + tt.into_iter().map(|tt| match tt { + TokenTree::Ident(mut ident) => { + self.visit_ident_mut(&mut ident); + TokenTree::Ident(ident) } - } else { - let span = path.segments[0].ident.span(); - let msg = "Self type of this impl is unsupported in expression position"; - let error = Error::new(span, msg).to_compile_error(); - *path = parse_quote!(::core::marker::PhantomData::<#error>); - } - } - - fn visit_token_stream(&self, tokens: &mut TokenStream) -> bool { - let mut out = Vec::new(); - let mut modified = false; - let mut iter = tokens.clone().into_iter().peekable(); - while let Some(tt) = iter.next() { - match tt { - TokenTree::Ident(mut ident) => { - modified |= prepend_underscore_to_self(&mut ident); - if ident == "Self" { - modified = true; - if self.as_trait.is_none() { - let ident = Ident::new("AsyncTrait", ident.span()); - out.push(TokenTree::Ident(ident)); - } else { - let self_ty = self.self_ty(ident.span()); - match iter.peek() { - Some(TokenTree::Punct(p)) - if p.as_char() == ':' && p.spacing() == Spacing::Joint => - { - let next = iter.next().unwrap(); - match iter.peek() { - Some(TokenTree::Punct(p)) if p.as_char() == ':' => { - let span = ident.span(); - out.extend(quote_spanned!(span=> <#self_ty>)); - } - _ => out.extend(quote!(#self_ty)), - } - out.push(next); - } - _ => out.extend(quote!(#self_ty)), - } - } - } else { - out.push(TokenTree::Ident(ident)); - } - } - TokenTree::Group(group) => { - let mut content = group.stream(); - modified |= self.visit_token_stream(&mut content); - let mut new = Group::new(group.delimiter(), content); - new.set_span(group.span()); - out.push(TokenTree::Group(new)); - } - other => out.push(other), + TokenTree::Group(group) => { + let tt = self.visit_token_stream(group.stream()); + let mut new = Group::new(group.delimiter(), tt); + new.set_span(group.span()); + TokenTree::Group(new) } - } - if modified { - *tokens = TokenStream::from_iter(out); - } - modified + tt => tt, + }).collect() } } -impl VisitMut for ReplaceReceiver { - // `Self` -> `Receiver` - fn visit_type_mut(&mut self, ty: &mut Type) { - if let Type::Path(node) = ty { - if node.qself.is_none() && node.path.is_ident("Self") { - *ty = self.self_ty(node.path.segments[0].ident.span()); - } else { - self.visit_type_path_mut(node); - } - } else { - visit_mut::visit_type_mut(self, ty); - } - } - - // `Self::Assoc` -> `::Assoc` - fn visit_type_path_mut(&mut self, ty: &mut TypePath) { - if ty.qself.is_none() { - self.self_to_qself_type(&mut ty.qself, &mut ty.path); - } - visit_mut::visit_type_path_mut(self, ty); - } - - // `Self::method` -> `::method` - fn visit_expr_path_mut(&mut self, expr: &mut ExprPath) { - if expr.qself.is_none() { - prepend_underscore_to_self(&mut expr.path.segments[0].ident); - self.self_to_qself_expr(&mut expr.qself, &mut expr.path); - } - visit_mut::visit_expr_path_mut(self, expr); - } - - fn visit_expr_struct_mut(&mut self, expr: &mut ExprStruct) { - self.self_to_expr_path(&mut expr.path); - visit_mut::visit_expr_struct_mut(self, expr); - } - - fn visit_pat_path_mut(&mut self, pat: &mut PatPath) { - if pat.qself.is_none() { - self.self_to_qself_expr(&mut pat.qself, &mut pat.path); +impl VisitMut for ReplaceSelf<'_> { + fn visit_ident_mut(&mut self, i: &mut Ident) { + if i == "self" { + *i = quote::format_ident!("{}{}", self.0, i); + #[cfg(self_span_hack)] + i.set_span(self.1); } - visit_mut::visit_pat_path_mut(self, pat); - } - - fn visit_pat_struct_mut(&mut self, pat: &mut PatStruct) { - self.self_to_expr_path(&mut pat.path); - visit_mut::visit_pat_struct_mut(self, pat); - } - fn visit_pat_tuple_struct_mut(&mut self, pat: &mut PatTupleStruct) { - self.self_to_expr_path(&mut pat.path); - visit_mut::visit_pat_tuple_struct_mut(self, pat); + visit_mut::visit_ident_mut(self, i); } fn visit_item_mut(&mut self, i: &mut Item) { - match i { - // Visit `macro_rules!` because locally defined macros can refer to `self`. - Item::Macro(i) if i.mac.path.is_ident("macro_rules") => { + // Visit `macro_rules!` because locally defined macros can refer to + // `self`. Otherwise, do not recurse into nested items. + if let Item::Macro(i) = i { + if i.mac.path.is_ident("macro_rules") { self.visit_macro_mut(&mut i.mac) } - // Otherwise, do not recurse into nested items. - _ => {} } } fn visit_macro_mut(&mut self, mac: &mut Macro) { - // We can't tell in general whether `self` inside a macro invocation - // refers to the self in the argument list or a different self - // introduced within the macro. Heuristic: if the macro input contains - // `fn`, then `self` is more likely to refer to something other than the - // outer function's self argument. - if !contains_fn(mac.tokens.clone()) { - self.visit_token_stream(&mut mac.tokens); - } - } -} - -fn contains_fn(tokens: TokenStream) -> bool { - tokens.into_iter().any(|tt| match tt { - TokenTree::Ident(ident) => ident == "fn", - TokenTree::Group(group) => contains_fn(group.stream()), - _ => false, - }) -} - -fn prepend_underscore_to_self(ident: &mut Ident) -> bool { - let modified = ident == "self"; - if modified { - *ident = Ident::new("_self", ident.span()); + mac.tokens = self.visit_token_stream(mac.tokens.clone()); + visit_mut::visit_macro_mut(self, mac); } - modified } diff --git a/src/respan.rs b/src/respan.rs deleted file mode 100644 index 38f6612..0000000 --- a/src/respan.rs +++ /dev/null @@ -1,22 +0,0 @@ -use proc_macro2::{Span, TokenStream}; -use quote::ToTokens; -use syn::parse::Parse; - -pub(crate) fn respan(node: &T, span: Span) -> T -where - T: ToTokens + Parse, -{ - let tokens = node.to_token_stream(); - let respanned = respan_tokens(tokens, span); - syn::parse2(respanned).unwrap() -} - -fn respan_tokens(tokens: TokenStream, span: Span) -> TokenStream { - tokens - .into_iter() - .map(|mut token| { - token.set_span(span); - token - }) - .collect() -} diff --git a/tests/executor/mod.rs b/tests/executor/mod.rs index f48b348..912fb79 100644 --- a/tests/executor/mod.rs +++ b/tests/executor/mod.rs @@ -4,6 +4,7 @@ use std::ptr; use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; // Executor for a future that resolves immediately (test only). +#[allow(clippy::missing_panics_doc)] pub fn block_on_simple(mut fut: F) -> F::Output { unsafe fn clone(_null: *const ()) -> RawWaker { unimplemented!() diff --git a/tests/test.rs b/tests/test.rs index 34205b4..53288f2 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -157,6 +157,73 @@ pub(crate) unsafe trait UnsafeTraitPubCrate {} #[async_trait] unsafe trait UnsafeTraitPrivate {} +pub async fn test_can_destruct() { + #[async_trait] + trait CanDestruct { + async fn f(&self, foos: (u8, u8, u8, u8)); + } + + #[async_trait] + impl CanDestruct for Struct { + async fn f(&self, (a, ref mut b, ref c, d): (u8, u8, u8, u8)) { + let _a: u8 = a; + let _b: &mut u8 = b; + let _c: &u8 = c; + let _d: u8 = d; + } + } +} + +pub async fn test_self_in_macro() { + #[async_trait] + trait Trait { + async fn a(self); + async fn b(&mut self); + async fn c(&self); + } + + #[async_trait] + impl Trait for String { + async fn a(self) { println!("{}", self); } + async fn b(&mut self) { println!("{}", self); } + async fn c(&self) { println!("{}", self); } + } +} + +pub async fn test_inference() { + #[async_trait] + pub trait Trait { + async fn f() -> Box> { + Box::new(std::iter::empty()) + } + } +} + +pub async fn test_internal_items() { + #[async_trait] + #[allow(dead_code, clippy::items_after_statements)] + pub trait Trait: Sized { + async fn f(self) { + struct Struct; + + impl Struct { + fn f(self) { + let _ = self; + } + } + } + } +} + +pub async fn test_unimplemented() { + #[async_trait] + pub trait Trait { + async fn f() { + unimplemented!() + } + } +} + // https://github.com/dtolnay/async-trait/issues/1 pub mod issue1 { use async_trait::async_trait; @@ -542,6 +609,7 @@ pub mod issue45 { } #[test] + #[should_panic] fn tracing() { // Create the future outside of the subscriber, as no call to tracing // should be made until the future is polled. @@ -1083,3 +1151,64 @@ pub mod issue134 { } } } + +// https://github.com/dtolnay/async-trait/pull/125#pullrequestreview-491880881 +pub mod drop_order { + use std::sync::atomic::{AtomicBool, Ordering}; + use async_trait::async_trait; + use crate::executor; + + struct Flagger<'a>(&'a AtomicBool); + + impl Drop for Flagger<'_> { + fn drop(&mut self) { + self.0.fetch_xor(true, Ordering::AcqRel); + } + } + + #[async_trait] + trait Trait { + async fn async_trait(_: Flagger<'_>, flag: &AtomicBool); + } + + struct Struct; + + #[async_trait] + impl Trait for Struct { + async fn async_trait(_: Flagger<'_>, flag: &AtomicBool) { + flag.fetch_or(true, Ordering::AcqRel); + } + } + + async fn standalone(_: Flagger<'_>, flag: &AtomicBool) { + flag.fetch_or(true, Ordering::AcqRel); + } + + #[async_trait] + trait SelfTrait { + async fn async_trait(self, flag: &AtomicBool); + } + + #[async_trait] + impl SelfTrait for Flagger<'_> { + async fn async_trait(self, flag: &AtomicBool) { + flag.fetch_or(true, Ordering::AcqRel); + } + } + + #[test] + fn test_drop_order() { + // 0 : 0 ^ 1 = 1 | 1 = 1 (if flagger then block) + // 0 : 0 | 1 = 1 ^ 1 = 0 (if block then flagger) + + let flag = AtomicBool::new(false); + executor::block_on_simple(standalone(Flagger(&flag), &flag)); + assert!(!flag.load(Ordering::Acquire)); + + executor::block_on_simple(Struct::async_trait(Flagger(&flag), &flag)); + assert!(!flag.load(Ordering::Acquire)); + + executor::block_on_simple(Flagger(&flag).async_trait(&flag)); + assert!(!flag.load(Ordering::Acquire)); + } +} diff --git a/tests/ui/delimiter-span.rs b/tests/ui/delimiter-span.rs index 68456fa..d3f67a1 100644 --- a/tests/ui/delimiter-span.rs +++ b/tests/ui/delimiter-span.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; macro_rules! picky { - (ident) => {}; + ($(t:tt)*) => {}; } #[async_trait] @@ -14,6 +14,7 @@ struct Struct; #[async_trait] impl Trait for Struct { async fn method() { + picky!({ 123, self }); picky!({ 123 }); } } diff --git a/tests/ui/delimiter-span.stderr b/tests/ui/delimiter-span.stderr index e080445..6120262 100644 --- a/tests/ui/delimiter-span.stderr +++ b/tests/ui/delimiter-span.stderr @@ -4,5 +4,14 @@ error: no rules expected the token `{` 3 | macro_rules! picky { | ------------------ when calling this macro ... -17 | picky!({ 123 }); +17 | picky!({ 123, self }); + | ^ no rules expected this token in macro call + +error: no rules expected the token `{` + --> $DIR/delimiter-span.rs:18:16 + | +3 | macro_rules! picky { + | ------------------ when calling this macro +... +18 | picky!({ 123 }); | ^ no rules expected this token in macro call diff --git a/tests/ui/lifetime-span.rs b/tests/ui/lifetime-span.rs new file mode 100644 index 0000000..4e9e5d9 --- /dev/null +++ b/tests/ui/lifetime-span.rs @@ -0,0 +1,36 @@ +use async_trait::async_trait; + +struct A; +struct B; + +#[async_trait] +pub trait Trait<'r> { + async fn method(&'r self); +} + +#[async_trait] +impl Trait for A { + async fn method(&self) { } +} + +#[async_trait] +impl<'r> Trait<'r> for B { + async fn method(&self) { } +} + +#[async_trait] +pub trait Trait2 { + async fn method<'r>(&'r self); +} + +#[async_trait] +impl Trait2 for A { + async fn method(&self) { } +} + +#[async_trait] +impl<'r> Trait2<'r> for B { + async fn method(&'r self) { } +} + +fn main() {} diff --git a/tests/ui/lifetime-span.stderr b/tests/ui/lifetime-span.stderr new file mode 100644 index 0000000..feae87f --- /dev/null +++ b/tests/ui/lifetime-span.stderr @@ -0,0 +1,46 @@ +error[E0726]: implicit elided lifetime not allowed here + --> $DIR/lifetime-span.rs:12:6 + | +12 | impl Trait for A { + | ^^^^^- help: indicate the anonymous lifetime: `<'_>` + +error[E0107]: this trait takes 0 lifetime arguments but 1 lifetime argument was supplied + --> $DIR/lifetime-span.rs:32:10 + | +32 | impl<'r> Trait2<'r> for B { + | ^^^^^^---- help: remove these generics + | | + | expected 0 lifetime arguments + | +note: trait defined here, with 0 lifetime parameters + --> $DIR/lifetime-span.rs:22:11 + | +22 | pub trait Trait2 { + | ^^^^^^ + +error[E0195]: lifetime parameters or bounds on method `method` do not match the trait declaration + --> $DIR/lifetime-span.rs:13:14 + | +8 | async fn method(&'r self); + | ---------------- lifetimes in impl do not match this method in trait +... +13 | async fn method(&self) { } + | ^^^^^^^^^^^^^ lifetimes do not match method in trait + +error[E0195]: lifetime parameters or bounds on method `method` do not match the trait declaration + --> $DIR/lifetime-span.rs:18:14 + | +8 | async fn method(&'r self); + | ---------------- lifetimes in impl do not match this method in trait +... +18 | async fn method(&self) { } + | ^^^^^^^^^^^^^ lifetimes do not match method in trait + +error[E0195]: lifetime parameters or bounds on method `method` do not match the trait declaration + --> $DIR/lifetime-span.rs:33:14 + | +23 | async fn method<'r>(&'r self); + | ---- lifetimes in impl do not match this method in trait +... +33 | async fn method(&'r self) { } + | ^^^^^^^^^^^^^^^^ lifetimes do not match method in trait diff --git a/tests/ui/self-span.stderr b/tests/ui/self-span.stderr index fb11528..9ea1bbe 100644 --- a/tests/ui/self-span.stderr +++ b/tests/ui/self-span.stderr @@ -1,12 +1,3 @@ -error[E0423]: expected value, found struct `S` - --> $DIR/self-span.rs:18:23 - | -3 | pub struct S {} - | --------------- `S` defined here -... -18 | let _: Self = Self; - | ^^^^ help: use struct literal syntax instead: `S {}` - error[E0308]: mismatched types --> $DIR/self-span.rs:17:21 | @@ -15,6 +6,12 @@ error[E0308]: mismatched types | | | expected due to this +error: the `Self` constructor can only be used with tuple or unit structs + --> $DIR/self-span.rs:18:23 + | +18 | let _: Self = Self; + | ^^^^ help: use curly brackets: `Self { /* fields */ }` + error[E0308]: mismatched types --> $DIR/self-span.rs:25:21 | diff --git a/tests/ui/send-not-implemented.stderr b/tests/ui/send-not-implemented.stderr index 05c445b..657b3b1 100644 --- a/tests/ui/send-not-implemented.stderr +++ b/tests/ui/send-not-implemented.stderr @@ -7,7 +7,7 @@ error: future cannot be sent between threads safely 10 | | let _guard = mutex.lock().unwrap(); 11 | | f().await; 12 | | } - | |_____^ future returned by `__test` is not `Send` + | |_____^ future created by async block is not `Send` | = help: within `impl Future`, the trait `Send` is not implemented for `MutexGuard<'_, ()>` note: future is not `Send` as this value is used across an await diff --git a/tests/ui/unreachable.rs b/tests/ui/unreachable.rs new file mode 100644 index 0000000..f546a58 --- /dev/null +++ b/tests/ui/unreachable.rs @@ -0,0 +1,20 @@ +#![deny(warnings)] + +use async_trait::async_trait; + +#[async_trait] +pub trait Trait { + async fn f() { + unimplemented!() + } +} + +#[async_trait] +pub trait TraitFoo { + async fn f() { + let y = unimplemented!(); + let z = y; + } +} + +fn main() {} diff --git a/tests/ui/unreachable.stderr b/tests/ui/unreachable.stderr new file mode 100644 index 0000000..0b74692 --- /dev/null +++ b/tests/ui/unreachable.stderr @@ -0,0 +1,14 @@ +error: unreachable statement + --> $DIR/unreachable.rs:16:9 + | +15 | let y = unimplemented!(); + | ---------------- any code following this expression is unreachable +16 | let z = y; + | ^^^^^^^^^^ unreachable statement + | +note: the lint level is defined here + --> $DIR/unreachable.rs:1:9 + | +1 | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(unreachable_code)]` implied by `#[deny(warnings)]` diff --git a/tests/ui/unsupported-self.stderr b/tests/ui/unsupported-self.stderr index c1ea955..c98807e 100644 --- a/tests/ui/unsupported-self.stderr +++ b/tests/ui/unsupported-self.stderr @@ -1,4 +1,4 @@ -error: Self type of this impl is unsupported in expression position +error: the `Self` constructor can only be used with tuple or unit structs --> $DIR/unsupported-self.rs:11:17 | 11 | let _ = Self;