Skip to content

Commit

Permalink
Support path arguments in autoimpl: Deref<Target = Foo>
Browse files Browse the repository at this point in the history
  • Loading branch information
dhardy committed Nov 17, 2022
1 parent 22f9b6f commit 6855b28
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 74 deletions.
62 changes: 49 additions & 13 deletions lib/src/autoimpl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -211,6 +220,7 @@ mod parsing {
}

let args = ImplArgs {
path_args: PathArguments::None,
ignores,
using,
clause,
Expand All @@ -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>(item) {
Ok(Item::Struct(item)) => item,
Ok(item) => {
Expand All @@ -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 => {
Expand All @@ -262,26 +283,33 @@ 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;
}
}
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`",);
}
}

Expand All @@ -307,22 +335,25 @@ 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 => {
emit_error!(span, "target requires argument `using self.FIELD`")
}
Error::CallSite(msg) => emit_error!(span, msg),
Error::WithSpan(span, msg) => emit_error!(span, msg),
Error::PathArgs(msg) => emit_error!(path_args_span, msg),
},
}
}
Expand All @@ -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<Target = T>`, this is `<Target = T>`.
/// This is always empty unless [`ImplTrait::support_path_args`] returns true.
pub path_args: PathArguments,
/// Fields ignored in attribute
pub ignores: Vec<Member>,
/// Field specified to 'use' in attribute
Expand Down
33 changes: 30 additions & 3 deletions lib/src/autoimpl/impl_using.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 `<Target = ..>`"));
}
match result {
Some(r) => r,
None => return Err(Error::PathArgs("expected `<Target = ..>`")),
}
}
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
}
Expand Down
132 changes: 74 additions & 58 deletions tests/newtype.rs
Original file line number Diff line number Diff line change
@@ -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: trait> &T, &mut T, Box<T>)]
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: trait + ?Sized> &T, &mut T, Box<T>, Rc<T>, Arc<T>)]
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: Foo>(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<Target = T>, DerefMut using self.0)]
pub struct NewFoo<T: Foo>(T);
impl<T: Foo> NewFoo<T> {
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<Target = dyn Foo + 'a> 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<Target = dyn Foo + 'a>, 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<T: Foo>(Box<T>);
#[autoimpl(Deref<Target = T>, DerefMut using self.0)]
pub struct BoxFoo<T: Foo>(Box<T>);
impl<T: Foo> BoxFoo<T> {
pub fn new(foo: Box<T>) -> Self {
BoxFoo(foo)
}
}

assert!(NewType(Box::new(S)).success());
}
#[autoimpl(Deref<Target = dyn Foo>, DerefMut using self.0)]
pub struct BoxDynFoo(Box<dyn Foo>);
impl BoxDynFoo {
pub fn new(foo: Box<dyn Foo>) -> Self {
BoxDynFoo(foo)
}
}

#[test]
fn new_foo_box_dyn() {
#[autoimpl(Deref, DerefMut using self.0)]
struct NewType(Box<dyn Foo>);
#[autoimpl(Deref<Target = dyn Foo>, DerefMut using self.0)]
pub struct RcDynFoo(Rc<dyn Foo>);
impl RcDynFoo {
pub fn new(foo: Rc<dyn Foo>) -> Self {
RcDynFoo(foo)
}
}

assert!(NewType(Box::new(S)).success());
#[autoimpl(Deref<Target = dyn Foo>, DerefMut using self.0)]
pub struct ArcDynFoo(Arc<dyn Foo>);
impl ArcDynFoo {
pub fn new(foo: Arc<dyn Foo>) -> Self {
ArcDynFoo(foo)
}
}
}

#[test]
fn new_foo_rc_dyn() {
#[autoimpl(Deref, DerefMut using self.0)]
struct NewType(Rc<dyn Foo>);

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<dyn Foo + Send + Sync>);

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());
}

0 comments on commit 6855b28

Please sign in to comment.