2

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
Joseph Garvin
  • 20,727
  • 18
  • 94
  • 165
  • No, that's not possible. Local variables are always hygienic. – Aloso Aug 08 '20 at 18:45
  • 2
    @Aloso: you can definitely bypass local variable hygiene with a proc macro, that's what Span::call_site is for AFAICT? The issue is a proc macro can't define a proc macro, so you can't extend that power to generated macros. – Joseph Garvin Aug 08 '20 at 18:50

0 Answers0