I am trying to create a simple library called derive_pattern. My ultimate goal is to be able to write something like this:
#[derive(Pattern)]
struct TestStruct {
x: i32,
y: i32,
}
#[test]
fn it_works() {
let test = TestStruct { x: 5, y: 10 };
match test {
// this macro should have been created by the derive,
// and should expand to a pattern that includes all of the field names
test_struct_pattern!() => {
// in this scope x and y should be defined because the pattern bound them
assert!(x == 5);
assert!(y == 5);
}
_ => unreachable!("pattern should have matched"),
}
}
I am curious whether or not it is possible. So far my attempts have failed. When defining a procedural macro it is possible to control the span of identifiers in order to bypass hygiene, but as far as I can tell there is no way for a procedural macro to define a new procedural macro. So instead my procedural macro has to generate macro_rules macros, and those end up enforcing hygiene.
Here is my procedural macro definition:
use inflector::Inflector;
use proc_macro2::Span;
use quote::quote;
use syn::{Fields, FieldsNamed, Ident, ItemStruct};
#[proc_macro_derive(Pattern)]
pub fn derive_pattern(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = proc_macro2::TokenStream::from(input);
let structure: ItemStruct = syn::parse2(input).expect("derive(Pattern) only works on structs.");
let name = structure.ident;
let macro_name = Ident::new(
&(name.to_string().to_snake_case() + "_pattern"),
name.span(),
);
match &structure.fields {
Fields::Named(FieldsNamed {
brace_token: _,
named,
}) => {
let mut field_names: Vec<Ident> = named
.iter()
.map(|f| f.ident.as_ref().unwrap().clone())
.collect();
for field_name in &mut field_names {
field_name.set_span(Span::call_site()); // try (and fail) to bypass hygiene
}
let output = quote! {
macro_rules! #macro_name {
() => {
#name{ #(#field_names),* }
}
}
};
output.into()
}
_ => panic!("derive(Pattern) only supports structs with named fields"),
}
}
I have uploaded a version with full Cargo.toml, etc to github: https://github.com/jgarvin/derive_pattern
The error I get is on the first assert in the test:
Compiling derive_pattern_test_cases v0.1.0 (/home/prophet/derive_pattern/derive_pattern_test_cases)
error[E0425]: cannot find value `x` in this scope
--> derive_pattern_test_cases/src/lib.rs:16:25
|
16 | assert!(x == 5);
| ^ not found in this scope
error[E0425]: cannot find value `y` in this scope
--> derive_pattern_test_cases/src/lib.rs:17:25
|
17 | assert!(y == 5);
| ^ not found in this scope