Search code examples
rustmacrosrust-macrosrust-proc-macros

Rust proc macro to do compile time checking if two types are equivelant


Hello I'm creating a proc macro that I can use in my declarative macros that checks if 2 types are the same, and if so, it will select the first block, otherwise it should select the second block. However I'm getting this error

error: macro expansion ignores token `{` and any following
  --> src\event_listener\macros.rs:25:13
   |
25 | /             funny_if!{
26 | |                 (),
27 | |                 (),
28 | |                 {
...  |
33 | |                 }
34 | |             }
   | |             ^ caused by the macro expansion here
   | |_____________|
   |
   |
   = note: the usage of `funny_if!` is likely invalid in item context

which means I'm doing some thing wrong, and I'm not sure at all what that is. If anyone could help that'd be great.

struct FunnyIf {
    type_to_match: Type,
    input_type: Type,
    true_block: Block,
    false_block: Block,
}

impl Parse for FunnyIf {
    fn parse(input: ParseStream) -> Result<Self> {
        let type_to_match: Type = input.parse()?;
        input.parse::<Token![,]>()?;
        let input_type: Type = input.parse()?;
        input.parse::<Token![,]>()?;
        let true_block: Block = input.parse()?;
        input.parse::<Token![,]>()?;
        let false_block: Block = input.parse()?;
        Ok(FunnyIf {
            type_to_match,
            input_type,
            true_block,
            false_block,
        })
    }
}

/// Creates a compile time if statement on types
#[proc_macro]
pub fn funny_if(input: TokenStream) -> TokenStream {
    let FunnyIf {
        type_to_match,
        input_type,
        true_block,
        false_block,
    } = parse_macro_input!(input as FunnyIf);
    let used_block = if type_to_match.to_token_stream().to_string()
        == input_type.to_token_stream().to_string()
    {
        true_block
    } else {
        false_block
    };
    let expanded = quote! {{
        #used_block
    }};
    expanded.into()
}

An example of this macro's usage if it's correctly implemented

macro_rules! example {
    ($typ:ty) => {
        funny_if! {
            (),
            $typ,
            { println!("type was ()"); },
            { println!("type wasn't ()"); }
        }
    }
}
fn main() {
    example!(()); // should print "type was ()"
    example!(u8); // should print "type wasn't ()"
}

Solution

  • I figured it out with the help of people on the rust community discord server! The code below is my latest iteration. The problem in my original code was that I was just returning the block, and putting it into the root scope, however that usage is invalid, I solved it by taking each statement from the block, and writing it to a TokenStream which I then returned from the function. I also created 2 other similar macros for types, and expressions if anyone is interested.

    struct If<T: Parse> {
        type_to_match: Type,
        input_type: Type,
        true_branch: T,
        false_branch: T,
    }
    
    impl<T: Parse> Parse for If<T> {
        fn parse(input: ParseStream) -> Result<Self> {
            let type_to_match: Type = input.parse()?;
            input.parse::<Token![,]>()?;
            let input_type: Type = input.parse()?;
            input.parse::<Token![,]>()?;
            let true_branch: T = input.parse()?;
            input.parse::<Token![,]>()?;
            let false_branch: T = input.parse()?;
            Ok(If {
                type_to_match,
                input_type,
                true_branch,
                false_branch,
            })
        }
    }
    
    /// Creates a compile time if statement
    /// that takes checks if 2 types are the same
    /// and if returns one of the branches
    #[proc_macro]
    pub fn block_if(input: TokenStream) -> TokenStream {
        let If {
            type_to_match,
            input_type,
            true_branch,
            false_branch,
        } = parse_macro_input!(input as If<Block>);
        let used_branch = if type_to_match.to_token_stream().to_string()
            == input_type.to_token_stream().to_string()
        {
            true_branch
        } else {
            false_branch
        }
        .stmts;
        let mut strm = TokenStream2::new();
        for stmt in used_branch {
            stmt.to_tokens(&mut strm)
        }
        strm.into()
    }
    
    /// Creates a compile time if statement
    /// that takes checks if 2 types are the same
    /// and if returns one of the branches
    #[proc_macro]
    pub fn type_if(input: TokenStream) -> TokenStream {
        let If {
            type_to_match,
            input_type,
            true_branch,
            false_branch,
        } = parse_macro_input!(input as If<Type>);
        let used_branch = if type_to_match.to_token_stream().to_string()
            == input_type.to_token_stream().to_string()
        {
            true_branch
        } else {
            false_branch
        };
        used_branch.into_token_stream().into()
    }
    
    /// Creates a compile time if statement
    /// that takes checks if 2 types are the same
    /// and if returns one of the branches
    #[proc_macro]
    pub fn expr_if(input: TokenStream) -> TokenStream {
        let If {
            type_to_match,
            input_type,
            true_branch,
            false_branch,
        } = parse_macro_input!(input as If<syn::Expr>);
        let used_branch = if type_to_match.to_token_stream().to_string()
            == input_type.to_token_stream().to_string()
        {
            true_branch
        } else {
            false_branch
        };
        used_branch.into_token_stream().into()
    }