Writing proc-macro without match statements everywhere

I have a lot of proc-macro code where only certain types of arguments are expected (no lifetimes, no references, generic arguments can only be of type Option, etc.).

Currently my code looks like a match statement after a match statement after a match statement.

Is there any way to make it more readable/more pleasant to write?

I want my code to read something like: convert this syn::Type to syn::TypePath. If it isn't then fail, then gracefully fail.

In particular, I want to avoid this code:

if ty_args.args.len() != 1 {
    panic!("Unsupported type: Unexpected path type: {ty:?}")
}

let ty_arg = &ty_args.args[0];
let syn::GenericArgument::Type(ty_arg) = ty_arg else {
    panic!("Unsupported type: Unexpected path type: {ty:?}")
};

let syn::Type::Path(ty_path_arg) = ty_arg else {
    panic!("Unsupported type: Unexpected path type: {ty:?}")
};

The only way to do that that I found is with the help of syn::parse_quote:

let args = &ty_args.args;
let ty_path_arg: syn::TypePath = syn::parse_quote! { #args };

But then the error is very cryptic, and I can only find where it happened with RUSTFLAGS="-Zproc-macro-backtrace"

I wrote this macro.

macro_rules! parse {
    ($value:expr) => {{
        let value = $value;
        syn::parse2(quote::quote_spanned! { syn::spanned::Spanned::span(&value) => #value })
    }};
}
pub(crate) use parse;

Which allows me to write code like this:

fn parse_property_type(value: syn::Expr) -> syn::Result<(syn::Ident, syn::Type)> {
    let value: syn::PathSegment = utils::parse!(value)?;

    let prop_name = value.ident;
    let prop_type: syn::AngleBracketedGenericArguments = utils::parse!(value.arguments)?;
    let prop_type: syn::Type = utils::parse!(prop_type.args)?;

    Ok((prop_name, prop_type))
}

Instead of doing that:

fn parse_property_type(value: syn::Expr) -> (syn::Ident, syn::Type) {
    let value: syn::Path = syn::parse_quote! { #value };
    assert_eq!(value.segments.len(), 1);
    let value = &value.segments[0];

    let prop_name = value.ident.clone();
    let syn::PathArguments::AngleBracketed(prop_type) = &value.arguments else {
        panic!();
    };
    let prop_type = &prop_type.args;
    let prop_type: syn::Type = syn::parse_quote! { #prop_type };

    (prop_name, prop_type)
}

I think it is slower, but it is so much easier to read.

Have you considered syn's parsing interface?

Wouldn't this result in the same problem, only inside parse functions?

I'd imagine your example to look like this when written with syn::parse::Parse:

use syn::parse::{Parse, ParseStream};
use syn::{Ident, Token, Type};

struct Prop {
    name: Ident,
    ty: Type,
}

impl Parse for Prop {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let name = input.parse()?;
        let _: Token![:] = input.parse()?;
        let ty = input.parse()?;
        
        Ok(Prop { name, ty })
    }
}

which I find to be quite readable.

1 Like

In this case I want to piggyback off of PathArguments syntax with PropertyName<PropertyType> syntax.

But you are right, this might be easier to do with parse functions:

impl Parse for Prop {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let name = input.parse()?;
        let _: Token![<] = input.parse()?;
        let ty = input.parse()?;
        let _: Token![>] = input.parse()?;
        Ok(Prop { name, ty })
    }
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.