Derive procedural macros using Lua scripts

Hi all,

I made a crate to give the ability to create derive macros using a Lua script. I didn't find any crate related to this use case (if there's one, please disregard this message).

The idea is simple: create a Lua table containing struct or enum metadata like field names, types, etc, and reinject back into Rust a string value created in this script.

If the community thinks this is a good idea or can be worthwhile, I'll publish it.

4 Likes

This sounds like a good idea, at least to me.

1 Like

Is the string from Lua parsed with syn to get a TokenStream for the proc macro to return? I feel like this might make it hard to have good diagnostics around the expanded code.

2 Likes

It certainly sounds like an interesting idea. How is Lua given access to the item that the derive is attached to?

1 Like

I feel this is the contrary: by being able to see directly what's going to be injected, using prints within the Lua code, you can spot (at least for myself) what's going on.

1 Like

You can browse the repo here: GitHub - dandyvica/luaproc: Create Rust derive macros in Lua

Here is a short example for generating anotehr Default implementation for a simple struct:

#[derive(Debug, LuaProc)]
#[cfg(target_os = "linux")]
#[luaproc("tests/point1.lua")]
struct Point1 {
    x: u16,
    y: u32,
}

and the corresponding Lua code:

-- unit tests
assert(struct.meta.ident == "Point1")
assert(struct.meta.attributes[1].name == "cfg")
assert(struct.meta.attributes[1].inner == 'target_os = "linux"')
assert(struct.meta.attributes[2].name == "luaproc")
assert(struct.meta.attributes[2].inner == '"tests/point1.lua"')
assert(struct.meta.generics.impl == "")
assert(struct.meta.generics.type == "")
assert(struct.meta.generics.where == "")

assert(struct.fields[1].name == "x")
assert(struct.fields[1].type == "u16")
assert(struct.fields[2].name == "y")
assert(struct.fields[2].type == "u32")

-- generate code this code:
--
-- impl Default for struct {
--     fn default() -> Self {
--         Self { x: u16::MAX, y: u32:MAX }
--     }
-- }

local fn = string.format(" Self { x: %s::MAX, y: %s::MAX }", struct.fields[1].type, struct.fields[2].type)
local impl = [[
impl Default for %s { 
    fn default() -> Self { %s } 
}
]]

-- return code to Rust
code = string.format(impl, struct.meta.ident, fn)
2 Likes

I'm talking about rustc-generated diagnostics, which (aiui) rely on hidden state within the TokenStreams that a proc macro pushes around to point to the right part of the macro input when a compilation error occurs. If your output TokenStream is parsed from a Lua-generated string, it's not connected to the macro input at all, and rustc can't figure out where the diagnostic spans should point. This affects end-users of the macro rather than the macro author.

I'm not necesssarily saying this is an unworkable idea, just pointing out a drawback that you should be aware of.

Edit: based on the example snippet you posted it seems like, in the intended usage of the crate, the macro author and the end-user are the same person. In that case it's less of a problem.

I understand your points and I'm totally aware that's not the best way to deal with proc macros.
And that's I'm saying in my github repo.

But for some cases, this could be a simpler solution, for people not already aware of the intricacies of the syn crate :wink:

1 Like