dtolnay/paste

Can't access local variable when on a nested macro

wolfiestyle opened this issue · 3 comments

It fails to use the local variable in [1], but it works fine in [2]

#[derive(Default, Debug)]
struct Test {
    val: i32,
}

impl Test {
    fn set_val(&mut self, arg: i32) {
        self.val = arg;
    }
}

macro_rules! setter {
    ($obj:expr, $field:ident, $value:expr) => {
        paste::expr! { $obj.[<set_ $field>]($value); }
    };

    // this should work [1]
    ($field:ident, $value:expr) => {{
        let mut new = Test::default();
        setter!(new, val, $value);
        new
    }};
}

fn main() {
    // error: cannot find value `new` in this scope
    let a = setter!(val, 42);
    println!("{:?}", a);
    // works fine this way [2]
    let b = {
        let mut new = Test::default();
        setter!(new, val, 42);
        new
    };
    println!("{:?}", b);
}

Tested on Rust 1.31.1 stable, Gentoo Linux x86_64.

This looks like a compiler bug. The input is being passed into the macro without Span information. We will need to minimize the bug and file it against the compiler.

Unfortunately the compiler's span handling is totally broken right now --- rust-lang/rust#43081.

TokenStream [
    Ident {
        ident: "new",
        span: #0 bytes(0..0)
    },
    Punct {
        ch: '.',
        spacing: Alone,
        span: #0 bytes(0..0)
    },
    Group {
        delimiter: Bracket,
        stream: TokenStream [
            Punct {
                ch: '<',
                spacing: Alone,
                span: #0 bytes(0..0)
            },
            Ident {
                ident: "set_",
                span: #0 bytes(0..0)
            },
            Ident {
                ident: "val",
                span: #0 bytes(0..0)
            },
            Punct {
                ch: '>',
                spacing: Alone,
                span: #0 bytes(0..0)
            }
        ],
        span: #0 bytes(0..0)
    },
    Group {
        delimiter: Parenthesis,
        stream: TokenStream [
            Literal {
                lit: Integer(42),
                suffix: None,
                span: #0 bytes(0..0)
            }
        ],
        span: #0 bytes(0..0)
    },
    Punct {
        ch: ';',
        spacing: Alone,
        span: #0 bytes(0..0)
    }
]

The code in #7 (comment) now builds successfully with Rust 1.45 and paste 1.0.

Correction: it builds with Rust 1.46+ (the current beta; releasing on August 27).