From 6855b288dd448297f8f8d32e0f26f65c456cf186 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 17 Nov 2022 11:05:37 +0000 Subject: [PATCH] Support path arguments in autoimpl: Deref --- lib/src/autoimpl.rs | 62 ++++++++++++---- lib/src/autoimpl/impl_using.rs | 33 ++++++++- tests/newtype.rs | 132 ++++++++++++++++++--------------- 3 files changed, 153 insertions(+), 74 deletions(-) diff --git a/lib/src/autoimpl.rs b/lib/src/autoimpl.rs index 8c85fe0..e4d18d2 100644 --- a/lib/src/autoimpl.rs +++ b/lib/src/autoimpl.rs @@ -12,7 +12,9 @@ use proc_macro_error::emit_error; use quote::{quote, TokenStreamExt}; use syn::spanned::Spanned; use syn::token::Comma; -use syn::{parse2, Field, Fields, Ident, Index, Item, ItemStruct, Member, Path, Token}; +use syn::{ + parse2, Field, Fields, Ident, Index, Item, ItemStruct, Member, Path, PathArguments, Token, +}; mod impl_misc; mod impl_using; @@ -46,6 +48,11 @@ pub trait ImplTrait { /// This path is matched against trait names in `#[autoimpl]` parameters. fn path(&self) -> SimplePath; + /// True if this target supports path arguments + fn support_path_args(&self) -> bool { + false + } + /// True if this target supports ignoring fields /// /// Default implementation: `false` @@ -133,6 +140,8 @@ pub enum Error { CallSite(&'static str), /// Emit an error with the given `span` and `message` WithSpan(Span, &'static str), + /// Emit an error regarding path arguments + PathArgs(&'static str), } /// Result type @@ -211,6 +220,7 @@ mod parsing { } let args = ImplArgs { + path_args: PathArguments::None, ignores, using, clause, @@ -226,10 +236,15 @@ impl ImplTraits { /// This attribute does not modify the item. /// The caller should append the result to `item` tokens. pub fn expand( - mut self, + self, item: Toks, find_impl: impl Fn(&Path) -> Option<&'static dyn ImplTrait>, ) -> Toks { + let ImplTraits { + mut targets, + mut args, + } = self; + let item = match parse2::(item) { Ok(Item::Struct(item)) => item, Ok(item) => { @@ -245,8 +260,14 @@ impl ImplTraits { let mut not_supporting_ignore = vec![]; let mut not_supporting_using = vec![]; - let mut impl_targets: Vec<(Span, _)> = Vec::with_capacity(self.targets.len()); - for target in self.targets.drain(..) { + let mut impl_targets: Vec<(Span, _, _)> = Vec::with_capacity(targets.len()); + for mut target in targets.drain(..) { + let target_span = target.span(); + let path_args = target + .segments + .last_mut() + .map(|seg| std::mem::take(&mut seg.arguments)) + .unwrap_or(PathArguments::None); let target_impl = match find_impl(&target) { Some(impl_) => impl_, None => { @@ -262,16 +283,23 @@ impl ImplTraits { if !target_impl.support_using() { not_supporting_using.push(target.clone()); } + if !(path_args.is_empty() || target_impl.support_path_args()) { + emit_error!( + target_span, + "target {} does not support path arguments", + target_impl.path() + ); + } - impl_targets.push((target.span(), target_impl)); + impl_targets.push((target.span(), target_impl, path_args)); } - if !self.args.ignores.is_empty() { + if !args.ignores.is_empty() { for (target, except_with) in not_supporting_ignore.into_iter() { if let Some(path) = except_with { if impl_targets .iter() - .any(|(_span, target_impl)| path == target_impl.path()) + .any(|(_, target_impl, _)| path == target_impl.path()) { continue; } @@ -279,9 +307,9 @@ impl ImplTraits { emit_error!(target, "target does not support `ignore`",); } } - if self.args.using.is_some() { + if args.using.is_some() { for target in not_supporting_using.into_iter() { - emit_error!(target, "`target does not support `using`",); + emit_error!(target, "target does not support `using`",); } } @@ -307,15 +335,17 @@ impl ImplTraits { } let mut toks = Toks::new(); - for mem in &self.args.ignores { + for mem in &args.ignores { check_is_field(mem, &item.fields); } - if let Some(mem) = self.args.using_member() { + if let Some(mem) = args.using_member() { check_is_field(mem, &item.fields); } - for (span, target) in impl_targets.drain(..) { - match target.struct_impl(&item, &self.args) { + for (span, target, path_args) in impl_targets.drain(..) { + let path_args_span = path_args.span(); + args.path_args = path_args; + match target.struct_impl(&item, &args) { Ok(items) => toks.append_all(items), Err(error) => match error { Error::RequireUsing => { @@ -323,6 +353,7 @@ impl ImplTraits { } Error::CallSite(msg) => emit_error!(span, msg), Error::WithSpan(span, msg) => emit_error!(span, msg), + Error::PathArgs(msg) => emit_error!(path_args_span, msg), }, } } @@ -332,6 +363,11 @@ impl ImplTraits { /// Arguments passed to [`ImplTrait`] implementation methods pub struct ImplArgs { + /// Path arguments to trait + /// + /// Example: if the target is `Deref`, this is ``. + /// This is always empty unless [`ImplTrait::support_path_args`] returns true. + pub path_args: PathArguments, /// Fields ignored in attribute pub ignores: Vec, /// Field specified to 'use' in attribute diff --git a/lib/src/autoimpl/impl_using.rs b/lib/src/autoimpl/impl_using.rs index da863f1..d521381 100644 --- a/lib/src/autoimpl/impl_using.rs +++ b/lib/src/autoimpl/impl_using.rs @@ -9,7 +9,7 @@ use super::{Error, ImplArgs, ImplTrait, Result}; use crate::SimplePath; use proc_macro2::TokenStream as Toks; use quote::quote; -use syn::ItemStruct; +use syn::{ItemStruct, PathArguments}; /// Implement [`core::borrow::Borrow`] pub struct ImplBorrow; @@ -126,16 +126,43 @@ impl ImplTrait for ImplDeref { SimplePath::new(&["", "core", "ops", "Deref"]) } + fn support_path_args(&self) -> bool { + true + } + fn support_using(&self) -> bool { true } fn struct_items(&self, item: &ItemStruct, args: &ImplArgs) -> Result<(Toks, Toks)> { if let Some(field) = args.using_field(&item.fields) { - let ty = field.ty.clone(); + let target = match args.path_args { + PathArguments::None => field.ty.clone(), + PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + ref args, + .. + }) => { + let mut result = None; + for arg in args { + if let syn::GenericArgument::Binding(b) = arg { + if b.ident == "Target" && result.is_none() { + result = Some(b.ty.clone()); + continue; + } + } + return Err(Error::PathArgs("expected ``")); + } + match result { + Some(r) => r, + None => return Err(Error::PathArgs("expected ``")), + } + } + PathArguments::Parenthesized(_) => return Err(Error::PathArgs("unexpected")), + }; + let member = args.using_member().unwrap(); let method = quote! { - type Target = #ty; + type Target = #target; fn deref(&self) -> &Self::Target { &self.#member } diff --git a/tests/newtype.rs b/tests/newtype.rs index c41e84e..9ef2792 100644 --- a/tests/newtype.rs +++ b/tests/newtype.rs @@ -1,78 +1,94 @@ //! Test implementing traits over newtype wrappers -use impl_tools::autoimpl; use std::rc::Rc; use std::sync::Arc; -#[autoimpl(for &T, &mut T, Box)] -trait Foo { - fn success(&self) -> bool; -} +mod inner { + use super::*; + use impl_tools::autoimpl; -struct S; -impl Foo for S { - fn success(&self) -> bool { - true + #[autoimpl(for &T, &mut T, Box, Rc, Arc)] + pub trait Foo { + fn is_true(&self) -> bool; } -} - -#[test] -fn direct() { - assert!(S.success()); -} - -#[test] -fn new_foo() { - #[autoimpl(Deref, DerefMut using self.0)] - struct NewType(T); - assert!(NewType(S).success()); -} - -#[test] -fn new_foo_ref() { - #[autoimpl(Deref, DerefMut using self.0)] - struct NewType<'a>(&'a dyn Foo); - - assert!(NewType(&S).success()); -} + #[autoimpl(Deref, DerefMut using self.0)] + pub struct NewFoo(T); + impl NewFoo { + pub fn new(foo: T) -> Self { + NewFoo(foo) + } + } -#[test] -fn new_foo_ref_mut() { - #[autoimpl(Deref, DerefMut using self.0)] - struct NewType<'a>(&'a mut dyn Foo); + #[autoimpl(Deref using self.0)] + pub struct FooRef<'a>(&'a dyn Foo); + impl<'a> FooRef<'a> { + pub fn new(foo: &'a dyn Foo) -> Self { + FooRef(foo) + } + } - assert!(NewType(&mut S).success()); -} + #[autoimpl(Deref, DerefMut using self.0)] + pub struct FooRefMut<'a>(&'a mut dyn Foo); + impl<'a> FooRefMut<'a> { + pub fn new(foo: &'a mut dyn Foo) -> Self { + FooRefMut(foo) + } + } -#[test] -fn new_foo_box() { - #[autoimpl(Deref, DerefMut using self.0)] - struct NewType(Box); + #[autoimpl(Deref, DerefMut using self.0)] + pub struct BoxFoo(Box); + impl BoxFoo { + pub fn new(foo: Box) -> Self { + BoxFoo(foo) + } + } - assert!(NewType(Box::new(S)).success()); -} + #[autoimpl(Deref, DerefMut using self.0)] + pub struct BoxDynFoo(Box); + impl BoxDynFoo { + pub fn new(foo: Box) -> Self { + BoxDynFoo(foo) + } + } -#[test] -fn new_foo_box_dyn() { - #[autoimpl(Deref, DerefMut using self.0)] - struct NewType(Box); + #[autoimpl(Deref, DerefMut using self.0)] + pub struct RcDynFoo(Rc); + impl RcDynFoo { + pub fn new(foo: Rc) -> Self { + RcDynFoo(foo) + } + } - assert!(NewType(Box::new(S)).success()); + #[autoimpl(Deref, DerefMut using self.0)] + pub struct ArcDynFoo(Arc); + impl ArcDynFoo { + pub fn new(foo: Arc) -> Self { + ArcDynFoo(foo) + } + } } -#[test] -fn new_foo_rc_dyn() { - #[autoimpl(Deref, DerefMut using self.0)] - struct NewType(Rc); - - assert!(NewType(Rc::new(S)).success()); +#[derive(Clone, Copy, Default)] +pub struct V(bool); +impl inner::Foo for V { + fn is_true(&self) -> bool { + self.0 + } } #[test] -fn new_foo_arc_dyn() { - #[autoimpl(Deref, DerefMut using self.0)] - struct NewType(Arc); - - assert!(NewType(Arc::new(S)).success()); +fn newtype() { + use inner::*; + + let mut v = V(true); + + assert!(v.is_true()); + assert!(NewFoo::new(v).is_true()); + assert!(FooRef::new(&v).is_true()); + assert!(FooRefMut::new(&mut v).is_true()); + assert!(BoxFoo::new(Box::new(v)).is_true()); + assert!(BoxDynFoo::new(Box::new(v)).is_true()); + assert!(RcDynFoo::new(Rc::new(v)).is_true()); + assert!(ArcDynFoo::new(Arc::new(v)).is_true()); }