-
-
Notifications
You must be signed in to change notification settings - Fork 296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proc macro attribute to iterate through struct's field names and types #516
Comments
I would write this as: // [package]
// name = "chinedu-example"
// version = "0.0.0"
// edition = "2018"
//
// [lib]
// proc-macro = true
//
// [dependencies]
// syn = "0.15"
// quote = "0.6"
extern crate proc_macro;
use self::proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, LitStr, Token};
struct RouteArgs {
path: String,
}
mod keyword {
syn::custom_keyword!(path);
}
impl Parse for RouteArgs {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<keyword::path>()?;
input.parse::<Token![=]>()?;
let path: LitStr = input.parse()?;
Ok(RouteArgs {
path: path.value(),
})
}
}
#[proc_macro_attribute]
pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as RouteArgs);
let input = parse_macro_input!(input as DeriveInput);
let fields = match &input.data {
Data::Struct(DataStruct { fields: Fields::Named(fields), .. }) => &fields.named,
_ => panic!("expected a struct with named fields"),
};
let field_name = fields.iter().map(|field| &field.ident);
let field_type = fields.iter().map(|field| &field.ty);
let path = args.path;
let struct_name = &input.ident;
TokenStream::from(quote! {
// Preserve the input struct unchanged in the output.
#input
impl #struct_name {
fn route() {
println!("{}", #path);
// The following repetition expands to, for example:
//
// let id: u32 = Default::default();
// let car_name: String = Default::default();
#(
let #field_name: #field_type = Default::default();
)*
}
}
})
} Here is the short test program I used: use chinedu_example::route;
#[route(path = "/users/:id/cars/:car_name")]
struct Foo {
id: u32,
car_name: String,
}
fn main() {
Foo::route();
} The implementation above uses a short use syn::{Lit, Meta, NestedMeta};
#[proc_macro_attribute]
pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as AttributeArgs);
assert_eq!(args.len(), 1);
let argument_name_and_value = match args.get(0) {
Some(NestedMeta::Meta(Meta::NameValue(meta))) => meta,
_ => panic!("expected argument `path = \"...\"`"),
};
assert_eq!(argument_name_and_value.ident, "path");
let path = match &argument_name_and_value.lit {
Lit::Str(lit) => lit.value(),
_ => panic!("path argument must be a string"),
};
/* ... */
} |
I AM SO EXCITED THANK YOU You are the most helpful human to exist ever (maybe an overstatement but that's how I feel right now) Thanks a lot. This plus the help that you gave me in real life is PLENTY to get my router attribute working.. CHEERS! |
Alright so I've circled back to this. I didn't go with my original plan - but these tips still informed the final implementation. I'm stuck on the last bit that I have left. I'm trying to dynamically generate a path based on some identifier called let route_handler = format!("self::__{}_mod__::{}::new()", route, route);
let route_handler = Ident::new(route_handler.as_str(), route.span());
let route_handler = quote! {
Box::new(#route_handler::new())
};
tokens.push(route_handler); So say I want this to create Of course, this code above doesn't work.
That makes sense. So, my question is, how can I create some tokens for my path, given that the path is dynamic so I can't just hard code it? Sorry if I'm missing something obvious! As always thanks so much for any pointers!! |
As long as you stick to creating only Idents that are valid identifiers, it should work. Something like: let route_mod_name = format!("__{}_mod__", route);
let route_mod = Ident::new(&route_mod_name, route.span());
quote! {
Box::new(self :: #route_mod :: #route :: new())
} |
Worked perfectly, thanks!!!!! |
Hey!
So I'm diving around the examples trying to figure out how to implement a macro. The examples and docs are very informative so thank you! But I figured I'd post an issue in case you immediately knew what things I should be looking at.
I want the above code to generate the following impl block:
So basically I want to get access to the
path
string as well as every field in my struct and the typeof that field.. Then I can use
quote
to generate some code.I'm going to dig around to see how I can accomplish this since I see some examples that do different bits of this but nothing that encompasses all of this (I don't think?).. but if ya see this in the meantime could ya point me in the right direction?
The text was updated successfully, but these errors were encountered: