I am quite stumped!
I have a macro, complicated yes, but I believe you can ignore most of its code, and still help me: If at the end of the macro definition, I @show the code it is to generate, it looks perfect. But if I invoque the macro, it injects zero code, so no function or variable created). If I launch @macroexpand on it: same, nothing to show. ![]()
Any ideas? (I have an awkward feeling this will turn out to be a very silly question!
).
Julia 1.11, Windows. I use Revise.jl, and yes I did restart Julia.
(Context, if you are curious: I am developing code that takes user-defined functions as input. If the user does this is a script, their input-function definition is reparsed at each script execution. Even if unchanged the input-function gets a new type, triggering recompilations. This code intends to work around this issue, while not imposing restrictions on type that would get in the way for forward automatic differentiation…)
using Printf, MacroTools
muscadeerror = error
struct Functor{name,Ta} <: Function
captured::Ta
function Functor{name}(;kwargs...) where{name}
nt = NamedTuple(kwargs)
return new{name,typeof(nt)}(nt)
end
end
# to debug, use 'Base.dump' on expressions
macro functor(capture,foo)
# Build capargname, a vector of names of captured variables, to later replace a -> o.captured.a in the body of foo -> (o::Functor{:foo})
if capture.head==:call # function call
if length(capture.args)==1 # no captured arguments
caparg = Any[]
capargname = Symbol[]
ncaparg = 0
else
if capture.args[2] isa Expr && capture.args[2].head isa Symbol && capture.args[2].head == :parameters # user not supposed to prefix captured args with ;, but I'm in a good mood
error("Do not use ; in list of captured arguments")
else
caparg = capture.args[2:end]
end
ncaparg = length(caparg)
capargname = Vector{Symbol}(undef,ncaparg)
for icaparg = 1:ncaparg
if caparg[icaparg] isa Symbol
capargname[icaparg] = caparg[icaparg]
elseif caparg[icaparg] isa Expr
if caparg[icaparg].head == :kw
capargname[icaparg] = caparg[icaparg].args[1]
elseif caparg[icaparg].head == :parameters
muscadeerror("Invalid @functor definition 3")
end
else
muscadeerror("Invalid @functor definition 4")
end
end
end
else
muscadeerror("Invalid @functor definition 2")
end
# Build the code for the method associated to the functor
# TODO all variables must be either capturedargs or fooargs, no closure. Throw error otherwise
foodict = splitdef(foo)
foodict[:body] = MacroTools.postwalk(foodict[:body]) do ex
ex isa Symbol && ex∈capargname ? :(o.captured.$ex) : ex # prefix captured args with `o.captured.` in method body
end
fooname = foodict[:name] # :foo
functortype = Expr(:curly,:Functor,QuoteNode(fooname))#:(Functor{$fooname}) # Functor{:foo}
foodict[:name] = Expr(:(::),:o,functortype) # (o::Functor{:foo}), name of the method that implements foo(x)
foo = combinedef(foodict) # code of said method
quotefoo = MacroTools.postwalk(rmlines,foo)
quotefoo = MacroTools.postwalk(unblock,quotefoo)
quotefoo = QuoteNode(quotefoo) # quote of unannotated code for said method, to decide wether foo-code changed or not
# obscure variable name, to prevent reparsing of the foo definition
tag = Symbol("tag_for_the_functor_macro_",fooname)
# build the code for the call to the functor constructor
caparg = Expr(:parameters,caparg...) # place a ; in front of the argument list (any prefixed ; was cleaned earlier)
constrcall = Expr(:call,functortype,caparg) # Functor{:foo}(;a,b=2)
constructfunctor = Expr(:(=),fooname,constrcall)
code = quote
$constructfunctor
if ~@isdefined($tag) || $tag ≠ $quotefoo
$tag = $quotefoo
$foo
end
end
@show prettify(code) # looks perfect
return code
end
####################
@macroexpand @functor with(a=2) cost7(x) = a*x # no output
@functor with(a=2) cost7(x) = a*x # no error
@show cost7(3) # error: what is cost7?