Procedural macros > execute an Expr or get Ident value


#1

Hello,

from within a procedural macro, is it possible to “execute” an Expr object to get it’s result or to get the value “represented” by an Ident?

I have a plugin that works on a literal String that I would like to apply on a ident or expression

thanks :slight_smile:


Edited below to add the code of the macro I’m trying to put together => Procedural macros > execute an Expr or get Ident value


#2

Do you want to evaluate the expression in the proc_macro handler itself (during compilation) or after code generation within the compiled executable? Could you give a short example how the macro should work?


#3

Sorry for my too short question indeed. Here is the simplified code on which I’m working (see below).

I created the say_hello!() compiler plugin which is basically calling HelloWorld::hello_world() by transforming a String literal into a type. I would now like to use that as part of a function using generics say_hello_untyped() getting the String from the function’s generic T. I was planning to get the type name using unsafe { std::intrinsics::type_name::<T>() }; by the plugin fails to work because it is receiving an identifier (name) and not its String value.

When calling say_hello!("HelloWorld") the plugin receives

entry = expr(4294967295: "HelloWorld")
entry.node = Lit(Spanned { node: Str(HelloWorld(96), Cooked), span: Span { lo: BytePos(1130), hi: BytePos(1142), ctxt: #29 } })

And when calling say_hello!(name) the plugin receives (rightfully so):

entry = expr(4294967295: name)
entry.node = Path(None, path(name))

Thus, I would either need to be able to evaluate the expression in the proc_macro itself or chain the proc_macro calls in a way that the say_hello!() macro is called with a String value and not an identifier.

I hope it is clearer. Again sorry for my too short / unstructured question and thanks in advance for your help


client.rs

#![feature(core_intrinsics, plugin)]
#![plugin(my_plugin)]

trait SayHello {
    fn hello_world() -> String {
        "Hello world !!!".to_string()
    }    
}
struct HelloWorld;
impl SayHello for HelloWorld {}

macro_rules! say_hello_typed {
    ($generic:ty) => {
        <$generic>::hello_world()
    };
}

fn say_hello_typed<T: SayHello>() -> String {
    say_hello_typed!(T)
}

/* doesn't compile if uncommented
fn say_hello_untyped<T: ?Sized + 'static>() -> String {
    let name = unsafe { std::intrinsics::type_name::<T>() };
    say_hello!(name) // say_hello! is a compiler plugin from `my_plugins` crate
}
*/

fn main() {
    assert_eq!(say_hello_typed::<HelloWorld>(), "Hello world !!!".to_string());
    assert_eq!(say_hello!("HelloWorld"), "Hello world !!!".to_string());

    //assert_eq!(say_hello_untyped::<HelloWorld>(), "Hello world !!!".to_string()); // <-- doesn't compile
}

lib.rs from my_plugin crate

#![crate_type = "dylib"]
#![feature(plugin_registrar, quote, rustc_private)]

extern crate rustc;
extern crate rustc_plugin;
extern crate syntax;

use rustc_plugin::Registry;
use syntax::ast::{self, Ident};
use syntax::codemap;
use syntax::ext::base::{self, ExtCtxt, MacResult, MacEager, DummyResult};
use syntax::ext::build::AstBuilder;
use syntax::parse::token::{ Lit, Token };
use syntax::tokenstream;
use syntax::print::pprust;
use syntax::fold::Folder;

/// Compiler hook register plugins
#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_macro("say_hello",say_hello);
}

pub fn say_hello(ecx: &mut ExtCtxt, sp: codemap::Span, args: &[tokenstream::TokenTree]) -> Box<MacResult + 'static> {
    if let Some(type_name_str) = parse(ecx, sp.clone(), args) {
        let type_name_ident = Ident::from_str(type_name_str.as_str());
        let output = quote_expr!(ecx, {
            <$type_name_ident>::hello_world()
        });
        MacEager::expr(output)
    } else {
        ecx.parse_sess().span_diagnostic.err(format!("failure parsing type name from {:?}", &args).as_str());
        DummyResult::any(sp)
    }
}

/// Looks for a single String literal and returns it.
/// Otherwise, logs an error with `ecx.span_err` and returns None.
fn parse(ecx: &mut ExtCtxt, sp: codemap::Span, args: &[tokenstream::TokenTree]) -> Option<String> {
    let mut parser = ecx.new_parser_from_tts(args);

    if let Ok(expr) = parser.parse_expr() {
        let entry = ecx.expander().fold_expr(expr);
        println!("entry = {:?}", &entry);
        println!("entry.node = {:?}", &entry.node);

        match entry.node {
            ast::ExprKind::Lit(ref lit) => {
                match lit.node {
                    ast::LitKind::Str(ref s, _) => Some(s.to_string()),
                    _ => {
                        ecx.span_err(entry.span, &format!(
                            "expected string literal but got `{}`",
                            pprust::lit_to_string(&**lit)));
                        None
                    },
                }
            },
            _ => {
                ecx.parse_sess().span_diagnostic.err("unsupported arguments");
                None
            }
        }
    } else {
        ecx.parse_sess().span_diagnostic.err("unable to parse arguments");
        None
    }
}

#4

Sorry but that seems like a conceptual problem, which is unlikely to be resolved.

We need to distinguish here between compile time and runtime. At compile time your macro handler is executed and is able to inspect and modify the code of a program on its syntax level (just arbitrary text symbols, no meaning). At runtime the modified code is then executed, which means that the CPU assigns meaning to the syntax by interpreting the binary code and doing some stuff.

What you want to do is to take the value/meaning of name which only exists at runtime and use it to generate a type identifier (a syntax element) which only exists at compile time within the macro handler. That type information is later interpreted and elided by the compiler.

So basically you can’t pass the information back from runtime to compile time, without some form of compilation/interpretation at runtime (which would “introduce multiple compile times”). Interpreted or JIT languages, e.g., Python, Java, can do such things as they are capable of reflection but Rust currently does not provide such facilities.

The two things you can do with an expression within a macro handler are:

  • you can evaluate a Expr object from compile time at runtime by simply integrating it in the generated code
  • you might evaluate a Expr object at compile time by interpreting the code yourself, but you have to provide the meaning to the Expr, i.e., variable values, what does ‘+’ mean, etc.

#5

Makes a lot of sense, thanks a lot for providing me with such a developed answer @mindsbackyard

I’ll see if I can find a workaround. Again, thanks