Proc macro: Get function signature from path

I want to supply a proc_macro_attribute with an attribute argument deserializer that takes a path to an exported function somewhere.

I need the signature of said function at a later point in time to determine the return type in order to implement a trait that will use this function.

Is it possible to extract a Signature or even a ReturnType from a function path annotation?

Example:

#[some_macro(deserializer = "serde_json::from_slice")]
struct ...

Should produce information about

fn from_slice(...) -> ...

The status quo

You'd have to start with helper traits, for this:

trait MyFnOnce<Args> { type Ret; }

impl_variadic! {
    _6 _5 _4 _3 _2 _1
}
// where
macro_rules! impl_variadic {(
    $($T:ident $($U:ident)*)?
) => (
    impl<F : FnOnce($($T $(, $U)*)?) -> R, R, $($T $(, $U)*)?>
       MyFnOnce<($($T, $($U),*)?)>
    for
        F
    {
        type Ret = R;
    }

    $(impl_variadic! { $($U)* })?
)} use impl_variadic;
  • (the macro ensures support for up to 6 args, but you can see it's easy to extend support for higher (albeit upper-bounded) arities).

With it, serde_json::from_slice::<T> will be —with T potentially inferred—, an instance of that MyFnOnce<_> trait, and so you could get its return type, but, again, in an inferred-only context (unless you were to know the exact types given as input to the function, that would help a lot).

In order to completely leave the value realm, and remain in the type real exclusively, you'll quickly be stuck, since there is no typeof! kind of builtin (sometimes part of it can be achieved in nightly with trait_alias_impl_trait).


The current "solution"

is to require the caller to provide such a return type themselves: should they put it wrong, you'll be surprised by how good the error message is in that case.


Maybe your case is manageable with more information than the one you provided. As I always say: macros are a tool that generates code, which means that to get the desired effect at least once, the macro is not necessary: "a macro expansion pass" can be manually written. If you did that, and then told us the inputs that should have lead to your hand-written code, we may be able to provide further assistance / a more detailed answer :slightly_smiling_face:

3 Likes

Hi @Yandros . Thanks for your detailed answer.

That's a neat trick guiding the signature using helper traits. Could I convince you to propose how one would look if I did have the T: Deserialize on hand? I am not restricted to an inferred-only context by design except for the error type.

That means that I know I can expect a Result<T, E> where only E should be derived entirely within the bounds of the macro.

To address your closing remarks allow me admit that as a fault on my behalf and to provide an example of a type of trait I would like the macro to implement for the type that the call-site provides through the attribute arguments:

trait ExtractFromSlice<E>
where
    E: From<Self::Error>,
{
    type SomeType;
    type SomeError;

    fn extract_from_slice(&self, slice: &[u8]) -> Result<Self::SomeType, Self::SomeError>;
}

where the macro accepts the exported function path, inspects the error component of its return type and implements it like such, requiring the caller only to provide the type and not the error from the deserialization function as such:

impl ExtractFromSlice<ConcreteType> for X {
    type SomeType = ConcreteType
    type SomeError = ??? <--- Macro should provide this

    fn extract_from_slice(&self, slice: &[u8]) -> Result<Self::SomeType, Self::SomeError> {
        serde_json::from_slice(slice)
    }
}

The macro accepts the type:

#[some_macro(deserializer = "serde_json::from_slice", type = ConcreteType)]
struct ...