Call macro created function/types from generated function. world age problem

I want to allow a user of my module to generate some types and functions via a macro which are then supported by generated functions.

For reasons, I am able to deduce constant scalar numbers from type information for a pair of types. Could be multiple scalars for a pair. I’d like to exploit this with generated functions to compute this total scalar at compilation time.

Two reasons for wanting this.
a) performance guarantees ( nothing is faster than if it is guaranteed to be constant scalar vs maybe jit figures out it’s a scalar )
b) simpler implementation. if I need to chain a bunch of these as Expr(:call)s vs reducing into a scalar the implementation becomes much simpler.

Simplified MWE

julia> module Foo
         macro gen(x)
           name = Symbol("foo"*string(x))
           typ = :(struct $name end)
           fun = :(foo(f::$name) = $x*$x)  
           Expr(:escape, Expr(:block, typ, fun))
         end

         struct foo0 end
         foo(x::foo0) = 0

         @gen(2)
           
         @generated function bar(a,b)
           v = foo(a())*foo(b())
           v
         end

         struct foo1 end
         foo(x::foo1) = 1

         @gen(3)
       end

julia> Foo.bar(Foo.foo0(),Foo.foo1())
ERROR: MethodError: no method matching Main.Foo.foo1()
The applicable method may be too new: running in world age 25591, while current world is 25595.
Closest candidates are:
  Main.Foo.foo1() at REPL[5]:19 (method too new to be called from this world context.)
...

julia> Foo.bar(Foo.foo0(),Foo.foo0())
0

julia> Foo.bar(Foo.foo0(),Foo.foo2())
0

julia> Foo.bar(Foo.foo0(),Foo.foo3())
ERROR: ...world age

julia> Foo.@gen(4); Foo.bar(Foo.foo4(),Foo.foo4());  # ultimate goal

I’ve seen a number of threads related to world age “work arounds” which I can’t quite get my head around. There are some packages out there as well ( GG.jl, FunctionWrappers ), not really sure how/if I can apply those to help.

Anyone have a recommendation/code example? I suspect my reason ‘a’ is not doable, but reason ‘b’ would still be nice to have a solution for.

Edit: specify that scalars are constant
Edit 2: I’ve got a working solution now.

I tried out FunctionWrappers and I seem to have a workable solution in my toy REPL example. Is this a reasonable way of doing this?

Second question. How do I avoid having to specify Main.foo, as it may not be Main where foo was created?

julia> module Foo
         using FunctionWrappers: FunctionWrapper
         macro gen(x)
           name = Symbol("foo"*string(x))
           typ = :(struct $name end)
           fun = :(foo(f::Type{$name}) = $x*$x)  
           Expr(:escape, Expr(:block, typ, fun))
         end

         @generated function bar(a,b)
           v = (FunctionWrapper{Int64,Tuple{Type{a}}}(Main.foo))(a)*(FunctionWrapper{Int64,Tuple{Type{b}}}(Main.foo))(b)
           v
         end
       end
Main.Foo

julia> Foo.@gen(2); Foo.@gen(3); Foo.bar(foo2(),foo3())
36

Sorry, I keep answering my own questions. I seem to to have figured it out. Assuming FunctionWrappers is a reasonable thing to use.

Final piece of the puzzle is to add a foo placeholder and then use a Foo. module scope in the macro

module Foo
         export foo
         using FunctionWrappers: FunctionWrapper

         macro gen(x)
           name = Symbol("foo"*string(x))
           typ = :(struct $name end)
           fun = :(Foo.foo(f::Type{$name}) = $x*$x)  
           Expr(:escape, Expr(:block, typ, fun))
         end
         foo(x) = 42
         @generated function bar(a,b)
           v = (FunctionWrapper{Int64,Tuple{Type{a}}}(foo))(a)*(FunctionWrapper{Int64,Tuple{Type{b}}}(foo))(b)
           v
         end
       end