I worked around the limitation of the displayed name with the new macro
macro redefinable(struct_def)
struct_def isa Expr && struct_def.head == :struct || error("struct definition expected")
if struct_def.args[2] isa Symbol
name = struct_def.args[2]
real_name = struct_def.args[2] = gensym(name)
elseif struct_def.args[2].head == :<:
name = struct_def.args[2].args[1]
real_name = struct_def.args[2].args[1] = gensym(name)
end
esc(quote
$struct_def
$name = $real_name # this should be `const $name = $real_name`
Base.show_datatype(io::Base.IO, ::Base.Type{$real_name}) = Base.print(io, $(QuoteNode(name)))
end)
end
A small example:
abstract type A end
foo(a::A) = (a, a.x)
@redefinable struct T <: A
x::Int
end
@show foo(T(42))
# (T(42), 42)
@redefinable struct T <: A
x::Float64
end
@show foo(T(3.14))
# (T(3.14), 3.14)
Again, this implements the semantic of Option 1: there are two independent types (secretly named ##T#***
) that know nothing about each other, and the name T
in the current namespace switches from one to the other.
Now, there is still a problem with this macro: the third to last line should be const $name = $real_name
, so that usage of $name
(T
in the example) would be inlined by the compiler.
Edit
In the macro, instead of the Base.show_datatype
hack, one can assign to $real_name.name.name
, that is, the return value of the macro becomes
esc(quote
$struct_def
$real_name.name.name = $(QuoteNode(name)) # fix the name
$name = $real_name # this should be `const $name = $real_name`
end)