如何为结构制作宏生成与结构匹配的函数方法?

sjp*_*sjp 2 macros julia

请原谅标题中任何令人困惑的术语,但想象一下我想要一个小宏来标记我创建的结构可用于某些恶意目的。我写了这个小模块:

module Usable

export @usable, isusable

isusable(::Type{T}) where T = false

macro usable(expr::Expr)
    name = expr.args[2]

    return quote
        $expr
        
        Usable.isusable(::Type{$name}) = true     # This in't working
    end
end

end
Run Code Online (Sandbox Code Playgroud)

但是,尝试使用我的宏

julia> include("Usable.jl")
Main.Usable

julia> using Main.Usable

julia> @usable struct Foo
           bar::String
       end
Run Code Online (Sandbox Code Playgroud)

结果是

ERROR: UndefVarError: Foo not defined
Run Code Online (Sandbox Code Playgroud)

该结构显然定义得很好

julia> Foo("soup")
Foo("soup")
Run Code Online (Sandbox Code Playgroud)

所以看起来这个定义比我预期的要早。我显然错过了一些东西,但我无法弄清楚是什么。

phi*_*ler 5

始终查看宏的输出:

julia> @macroexpand @usable struct Foo
                  bar::String
              end
quote
    #= REPL[1]:11 =#
    struct Foo
        #= REPL[4]:2 =#
        bar::Main.Usable.String
    end
    #= REPL[1]:13 =#
    (Main.Usable.Usable).isusable(::Main.Usable.Type{Main.Usable.Foo}) = begin
            #= REPL[1]:13 =#
            true
        end
end
Run Code Online (Sandbox Code Playgroud)

问题是宏输出在它定义的模块内展开,这弄乱了名称的含义。在这种情况下,我们想要Foo引用定义它的命名空间,但是:

quote
    #= REPL[1]:11 =#
    struct Foo
        #= REPL[10]:2 =#
        bar::String
    end
    #= REPL[1]:13 =#
    Usable.isusable(::Type{Foo}) = begin
            #= REPL[1]:13 =#
            true
        end
end
Run Code Online (Sandbox Code Playgroud)

得到它实际上很简单 - 只需转义输出:

macro usable(expr::Expr)
   name = expr.args[2]

   return esc(quote
       $expr
       $Usable.isusable(::Type{$name}) = true
   end)
end
Run Code Online (Sandbox Code Playgroud)

但是请再次阅读宏文档。 esc非常复杂,您不想盲目地将它应用到您编写的所有内容中。

另一件事(我希望能做到这一点)是拼接模块本身 -- $Usable-- 而不是按名称引用它。否则,如果在外部重命名模块名称,则可能会出现问题。