How to construct syntax trees robustly?

I'm writing a crate that heavily depends on codegen by proc macros, and I'm running into a bit of tension between the ease of using quote! to produce the syn syntax nodes I want to output, and the fact that I have no guarantees that the substitutions I'm making with that macro are actually valid, e.g. my macro might take a Path as input and then substitute it a place where only an Ident is allowed and I didn't notice because my examples are one-item paths. Now, obviously, testing is valuable to catch this, but I've come to expect firmer guarentees from the ecosystem, so can anyone tell me if there's an approach that avoids this sort of square-peg-in-round-hole issue that's more ergonomic than hand-crafting syntax nodes?

To illustrate what I mean, my code is a step removed from directly calling parse_quote, as I have this macro:

macro_rules! fallible_quote {
	($($tt:tt)*) => {
        {
            use syn::parse::Parser;
            let toks = quote::quote! { $($tt)* };
		    syn::parse::Parse::parse.parse2(toks.clone()).map_err(|e| {
			    syn::parse::Error::new(
				    e.span(),
				    format!("internal error: {} at file {}:{} - this is likely a bug\n{toks}", e, file!(), line!()),
	    		)
    		})
        }
	};
}

This means that in the event I do make a bad substitution and the result isn't valid as the type I'm expecting, I can report the error within my crate rather than at my caller's invocation site. But is there any way to catch this sort of issue at compile time, or otherwise be more sure that the transforms I'm doing are always valid no matter how weird an input I'm given? (at least, once that input is parsed - that stage failing is expected and easy to deal with)

I'd you want something that is truly type-safe and guaranteed to be valid, I guess you could always construct syn AST structs manually. That will ensure everything is valid at compile time with the minor drawback of requiring 10x more code to express something.

1 Like

There is syn::parse_quote!. This ensures everything is valid locally, but means you're redundantly parsing the tokens — it's equivalent (from what I can see in the declaration) to quote! followd by parsing the result.

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.