Statically asserting an ident is a variable, not a type

I've written a proc macro that needs to know whether an ident is a variable, as opposed to a type, function or enum variant. It has some heuristics that work 90% of the time; the rest of the time it relies on explicit disambiguation by the programmer. I'd like to assert the correct inference has been made by statically asserting so in the output AST.

I'm currently using something like the following, which generally works, but it can in some cases introduce a borrowck error like the one below. Is there any implementation of assert_variable that statically fails for assert_variable!(main) and assert_variable!(None) but succeeds for assert_variable!(x) in this example?

macro_rules! assert_variable {
    ($var:ident) => {
        #[allow(unreachable_code)]
        {
            if false {
                $var = loop {};
            }
        }
    }
}

fn main() {
    // assert_variable!(main);
    // assert_variable!(None);
    
    let x = 10usize;
    
    let _left = async {
        assert_variable!(x);
    };
    let _right = async {
        assert_variable!(x);
    };
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0524]: two closures require unique access to `x` at the same time
  --> src/main.rs:21:24
   |
6  |                   $var = loop {};
   |                   ----
   |                   |
   |                   first borrow occurs due to use of `x` in generator
   |                   second borrow occurs due to use of `x` in generator
...
18 |       let _left = async {
   |  _______________________-
19 | |         assert_variable!(x);
20 | |     };
   | |_____- first closure is constructed here
21 |       let _right = async {
   |  ________________________^
22 | |         assert_variable!(x);
23 | |     };
   | |_____^ second closure is constructed here
24 |   }
   |   - first borrow might be used here, when `_left` is dropped and runs the destructor for type `impl std::future::Future`

error: aborting due to previous error

error: could not compile `playground`.

To learn more, run the command again with --verbose.

Can you give some more detail about the purpose of the proc-macro and why it needs to know this?

Sure, it's for my crate serde_closure. It lets you wrap a closure with Fn!(...) to make it debuggable and serializable. It works by extracting captured variables from the AST and putting them in a struct that implements Serialize, Deserialize, Debug and other convenient traits.

2 Likes