Say I want to define a macro which adds a hidden field to a user defined type. See the minimal working example below
module Foo
""" Modify ex by pushing hidden field. """
function push_hidded_field!(ex)
paramex = ex.args[2]
bodyex = ex.args[3]
push!(bodyex.args, :(y::S))
push!(paramex.args, :S)
ex
end
macro deftype(ex)
push_hidded_field!(ex)
quote
$ex
end |> esc
end
export @deftype
end
Now consider some of the use case of this macro.
using .Foo
# Initial definiton is not a problem.
@deftype struct Bar{T}
x::T
end
There is nothing wrong in this case.
julia> dump(Bar)
UnionAll
var: TypeVar
name: Symbol T
lb: Union{}
ub: Any
body: UnionAll
var: TypeVar
name: Symbol S
lb: Union{}
ub: Any
body: Bar{T,S} <: Any
x::T
y::S
julia> fieldnames(Bar)
(:x, :y)
Bar
was defined as expexted with a hidden fieldname y
. The problem occurs when I try to parameterize x
with S
.
julia> @deftype struct Bar{S}
x::S
end
ERROR: syntax: function static parameter names not unique around /tmp/deleteme4.jl:17
Stacktrace:
[1] top-level scope at REPL[7]:1
[2] include_string(::Function, ::Module, ::String, ::String) at ./loading.jl:1088
The complaint is that the names are not unique. This means that the user calling the macro cannot parametrize its fieldname x
with S
. I think this is restrictive and needs a better approach.
To solve this problem, let us consider that we parameterize our hidden field name y
with gensym
so that we avoid duplicate names. So let us redefine the macro
module Foo
""" Modify ex by pushing hidden field. """
function push_hidded_field!(ex)
paramex = ex.args[2]
bodyex = ex.args[3]
S = gensym() # Hidden parameter type be different with those of the user.
push!(bodyex.args, :(y::$S))
push!(paramex.args, S)
ex
end
""" Define the macro """
macro deftype(ex)
push_hidded_field!(ex)
quote
$ex
end |> esc
end
export @deftype
end
and let us try some use cases.
julia> using .Foo
julia> @deftype struct Bar{T}
x::T
end
julia> dump(Bar)
UnionAll
var: TypeVar
name: Symbol T
lb: Union{}
ub: Any
body: UnionAll
var: TypeVar
name: Symbol S
lb: Union{}
ub: Any
body: Bar{T,S} <: Any
x::T
y::S
julia> fieldnames(Bar)
(:x, :y)
There is nothing wrong in the first call. The problem occurs when the macro is called multiple times in the same julia session.
julia> @deftype struct Bar{T}
x::T
end
ERROR: invalid redefinition of constant Bar
Stacktrace:
[1] top-level scope at REPL[1]:16
[2] include_string(::Function, ::Module, ::String, ::String) at ./loading.jl:1088
This time the problem is that after being defined Bar
is constant and cannot be redefined.
Any suggestions about how to get rid of these restrictions?