rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.7k stars 12.75k forks source link

Different behavior for macro by example and procedural macro #125238

Closed thomasyonug closed 6 months ago

thomasyonug commented 6 months ago

I tried this code:

// procedural macro definition
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn;

#[proc_macro]
pub fn mdriver(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_mdriver(&ast)
}

fn impl_mdriver(ast: &syn::Expr) -> TokenStream {
    let generated = quote! {
        pub fn main() {
            let (tx, rx) = #ast::<usize>();
            let x: Box<isize> = Box::new(1);
            let x_in_parent = &(*x) as *const isize as usize;
            let t = std::thread::spawn(move || {
                let x_in_child = &(*x) as *const isize as usize;
                tx.send(x_in_child).unwrap();
            });
            let x_in_child = rx.recv().unwrap();
            assert_eq!(x_in_parent, x_in_child);
            t.join().unwrap();
        }
    };

    generated.into()
}

// procedural call
mdriver!(std::sync::mpsc::channel);

// macro by example definition

#[macro_export]
macro_rules! mdriver {
    ($channel:expr) => {
        pub fn main() {
            let (tx, rx) = $channel::<usize>();
            let x: Box<isize> = Box::new(1);
            let x_in_parent = &(*x) as *const isize as usize;
            let t = std::thread::spawn(move || {
                let x_in_child = &(*x) as *const isize as usize;
                tx.send(x_in_child).unwrap();
            });
            let x_in_child = rx.recv().unwrap();
            assert_eq!(x_in_parent, x_in_child);
            t.join().unwrap();
        }
    };
}

// macro by example call

mdriver!(std::sync::mpsc::channel);

I expected to see this happen:

// expanded code as follows
#[prelude_import]
use ::std::prelude::rust_2015::*;
#[macro_use]
extern crate std;
extern crate pcd_16652092204041976610;
use pcd_16652092204041976610::mdriver;

fn main() {
        let (tx, rx) = std::sync::mpsc::channel::<usize>();
        let x: Box<isize> = Box::new(1);
        let x_in_parent = &*x as *const isize as usize;
        let t =
            std::thread::spawn(move ||
                    {
                            let x_in_child = &*x as *const isize as usize;
                            tx.send(x_in_child).unwrap();
                        });
        let x_in_child = rx.recv().unwrap();
        match (&x_in_parent, &x_in_child) {
                (left_val, right_val) => {
                    if !(*left_val == *right_val)
                            {
                                    let kind = ::core::panicking::AssertKind::Eq;
                                    ::core::panicking::assert_failed(kind, &*left_val,
                                        &*right_val, ::core::option::Option::None);
                                }
                        } };
                t.join().unwrap();
            }

Instead, this happened:

// but for macro by example, I get an error
// error: expected one of `.`, `;`, `?`, `else`, or an operator, found `::`
fn main() { (/*ERROR*/) }

Meta

rustc --version --verbose:

rustc 1.80.0-nightly (ef0027897 2024-05-12)
binary: rustc
commit-hash: ef0027897d2e9014766fb47dce9ddbb925d2f540
commit-date: 2024-05-12
host: x86_64-unknown-linux-gnu
release: 1.80.0-nightly
LLVM version: 18.1.4
Backtrace

``` ```

bjorn3 commented 6 months ago

$channel::<usize>() is not a valid expression as $channel is parsed as expression due to the $channel:expr match. Try matching it using $($channel:tt)* and then use $($channel)*::<usize>().

This doesn't happen with proc macros as the syn::Expr gets turned back into a bunch of token trees rather than being captured as an expression like happens in the decl macro (macro by example) case.