I’m again struggling with correct macro syntax. I have a macro that is doing it’s job as required but relies on eval(), which I understand is not ideal.
The macro should insert fields of an existing struct into a new struct that is constructed with Base.@kwdef. For testing purposes, I rely on the @kwredef macro from RedefStructs.
using RedefStructs
macro mixin(expr)
x = eval(expr)
T = typeof(x)
values = [getfield(x, f) for f in fieldnames(T)]
output = Expr(:block)
for (f, type, v) in zip(fieldnames(T), fieldtypes(T), values)
push!(output.args, :($(esc(f))::$type = $v) )
end
:($output)
end
@kwredef mutable struct TabMixin
tab::String = "mail"
animate::Bool = false
end
@kwredef mutable struct Example
name::String = "Me"
button::Bool = true
@mixin TabMixin()
end
ex = Example()
propertynames(ex)
I tried to move all into the quote part, but I failed…
macro mixin(expr)
quote
local x = $expr
local T = typeof(x)
local values = [Stipple.Observables.to_value(getfield(x, f)) for f in fieldnames(T)]
local output = Expr(:block)
for (f, type, v) in zip(fieldnames(T), fieldtypes(T), values)
push!(output.args, :( ($(esc(f)))::$type = $v) )
end
output
end
end
I don’t think this can work without the eval because the only info the macro gets is :TabMixin which is just a Symbol and does not contain any information on the type-def which used that same symbol. Going beyond that you’d need something like a generated type (akin to a generated function) which does not exist.
One possibility would be if you define a custom macro upon definition of TableMixin (via decorating it with some other macro), this would mean though that you can only mixin structs which were created thus. Would look something like
@mixinable struct TabMixin
a
b
end
# -> this would, besided the typedef create a macro, say @mixin_TabMixin
struct Example
name
button
@mixin_TabMixin
end
This is a similar to the @unpack_MyType macro that Parameters.jl’s @with_kw macro creates. Maybe you can find inspiration there.
Thank you all for your comments. I took me a while to digest and to further work on this.
Finally I succeeded to build at least a version that can handle evaluation at the calling scope, which I previously didn’t know how to achieve.
Today I found out that @mauro3 already pointed me in the right direction with __module__ bit I didn’t get it at that time.
This is what I ended up with:
macro mixin(expr, prefix = "", postfix = "")
if hasproperty(expr, :head) && expr.head == :(::)
prefix = string(expr.args[1])
expr = expr.args[2]
end
x = Core.eval(__module__, expr)
pre = Core.eval(__module__, prefix)
post = Core.eval(__module__, postfix)
T = x isa DataType ? x : typeof(x)
mix = x isa DataType ? x() : x
values = getfield.(Ref(mix), fieldnames(T))
output = quote end
for (f, type, v) in zip(Symbol.(pre, fieldnames(T), post), fieldtypes(T), values)
push!(output.args, :($f::$type = Stipple._deepcopy($v)) )
end
esc(:($output))
end