Skip to content

Commit

Permalink
Properly delegate generic method type and lifetime arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
Kobzol committed Oct 4, 2024
1 parent 0f61120 commit 8c16fad
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Dev

- Correctly pass generic method type and lifetime arguments to the delegated method.

# 0.13.0 (2. 9. 2024)

- Generalize match arms handling. You can now combine a match expression target with annotations like `#[into]` and
Expand Down
41 changes: 37 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ use quote::{quote, ToTokens};
use syn::parse::ParseStream;
use syn::spanned::Spanned;
use syn::visit_mut::VisitMut;
use syn::{parse_quote, Error, Expr, ExprField, ExprMethodCall, FnArg, Meta};
use syn::{parse_quote, Error, Expr, ExprField, ExprMethodCall, FnArg, GenericParam, Meta};

use crate::attributes::{
combine_attributes, parse_method_attributes, parse_segment_attributes, ReturnExpression,
Expand Down Expand Up @@ -846,13 +846,46 @@ pub fn delegate(tokens: TokenStream) -> TokenStream {
.generate_await
.unwrap_or_else(|| method.method.sig.asyncness.is_some());

// fn method<'a, A, B> -> method::<'a, A, B>
let generic_params = &method.method.sig.generics.params;
let generics = if generic_params.is_empty() {
quote::quote! {}
} else {
let span = generic_params.span();
let mut params: syn::punctuated::Punctuated<
proc_macro2::TokenStream,
syn::Token![,],
> = syn::punctuated::Punctuated::new();
for param in generic_params.iter() {
let token = match param {
GenericParam::Lifetime(l) => {
let token = &l.lifetime;
let span = l.span();
quote::quote_spanned! {span=> #token }
}
GenericParam::Type(t) => {
let token = &t.ident;
let span = t.span();
quote::quote_spanned! {span=> #token }
}
GenericParam::Const(c) => {
let token = &c.ident;
let span = c.span();
quote::quote_spanned! {span=> #token }
}
};
params.push(token);
}
quote::quote_spanned! {span=> ::<#params> }
};

let modify_expr = |expr: &Expr| {
let body = if let Some(target_trait) = &attributes.target_trait {
quote::quote! { #target_trait::#name(#expr, #(#args),*) }
quote::quote! { #target_trait::#name#generics(#expr, #(#args),*) }
} else if is_method {
quote::quote! { #expr.#name(#(#args),*) }
quote::quote! { #expr.#name#generics(#(#args),*) }
} else {
quote::quote! { #expr::#name(#(#args),*) }
quote::quote! { #expr::#name#generics(#(#args),*) }
};

let mut body = if generate_await {
Expand Down
81 changes: 81 additions & 0 deletions tests/generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use delegate::delegate;

#[test]
fn test_generics_method() {
struct Foo;
impl Foo {
fn foo<'a, P, X>(&self) {}
}

struct Bar(Foo);
impl Bar {
delegate! {
to &self.0 {
fn foo<'a, P, X>(&self);
}
}
}
}

#[test]
fn test_generics_function() {
struct Foo;
impl Foo {
fn foo<P, X>() {}
}

struct Bar(Foo);
impl Bar {
delegate! {
to Foo {
fn foo<P, X>();
}
}
}
}

#[test]
fn test_generics_through_trait() {
trait A {
fn f<P>(&self) -> u32;
}

struct Foo;

impl A for Foo {
fn f<P>(&self) -> u32 {
0
}
}

struct Bar(Foo);

impl Bar {
delegate! {
to &self.0 {
#[through(A)]
fn f<P>(&self) -> u32;
}
}
}

let bar = Bar(Foo);
assert_eq!(bar.f::<u32>(), 0);
}

#[test]
fn test_generics_complex() {
struct Foo;
impl Foo {
fn foo<'a: 'static, X: Copy, #[allow(unused)] T>(&self) {}
}

struct Bar(Foo);
impl Bar {
delegate! {
to &self.0 {
fn foo<'a: 'static, X: Copy, #[allow(unused)] T>(&self);
}
}
}
}

0 comments on commit 8c16fad

Please sign in to comment.