Trying to get an Expr from a type

Hi all. I have a variable bar::DataType and I want to get the expression of that datatype, as follows.

bar = Foo{T}
Expr(bar) # :(Foo{T}).

However, this gives an error, and I cannot seem to get it through interpolation:

struct Foo{T} end
bar = Foo{Float64}

:($bar) # Foo{Float64}
:(Foo{Float64}) #:(Foo{Float64})
(quote $bar end).args[2] # Foo{Float64}
(quote Foo{Float64} end).args[2] # :(Foo{Float64})

What do I do?

Generally, instances don’t contain or reference the expression was evaluated to make them, that’s why we use reflection to track down method bodies. Also, interpolation inserts those instances or references to them into an expression, it doesn’t inverse-evaluate them into subexpressions. Inverse is probably the wrong word in the mathematical sense because evaluation is not bijective (many expressions can evaluate to the same thing, so which should be picked).

julia> :(a, $(Ref{Int})) |> dump
Expr
  head: Symbol tuple
  args: Array{Any}((2,))
    1: Symbol a
    2: Ref{Int64} <: Any

julia> :(a, Ref{Int}) |> dump
Expr
  head: Symbol tuple
  args: Array{Any}((2,))
    1: Symbol a
    2: Expr
      head: Symbol curly
      args: Array{Any}((2,))
        1: Symbol Ref
        2: Symbol Int

On the other hand, it doesn’t look like you need reflection or an inverse evaluation because you’re not after the full definition struct Foo{T} end and its methods. Your intended result is pretty simple that we could just piece it together. Bear in mind this digs into internal details that can change across minor revisions, so it’s worth checking them, like fieldnames(typeof(bar)).

julia> unsetparameters(T::Type) = Base.typename(T).wrapper
unsetparameters (generic function with 1 method)

julia> struct Foo{T} end

julia> unsetparameters(Foo)
Foo

julia> unsetparameters(Foo{Float64}) # not sure if works elsewhere
Foo

julia> function expressunion(T::UnionAll)
         params = Symbol[]
         U = T
         while U isa UnionAll
           push!(params, Symbol(U.var))
           U = U{U.var} # isa DataType with enough TypeVars
         end
         :( $(Symbol(T)){ $(params...) } )
       end
expressunion (generic function with 1 method)

julia> expressunion(Foo)
:(Foo{T})

julia> expressunion(unsetparameters(Foo{Float64}))
:(Foo{T})

No idea what you’re going to do with that but good luck.

1 Like

ExprTools has a function for this.
It’s not exported.

julia> using ExprTools

julia> ExprTools.name_of_type(Foo{Float64})
:(Main.Foo{Float64})

julia> using ExprTools

julia> struct Foo{T} end

julia> expr = ExprTools.name_of_type(Foo{Float64})
:(Main.Foo{Float64})

It does qualify it with the module because that is needful sometimes.

Its the much more complex and comprehensive version of @Benny 's code above.

2 Likes

Thanks! For context, what I was trying to do was to define a struct from a collection of other concrete types, such that the fields of the new type was the disjoint union of the fields of the input types:

struct Foo{T}
  foo1::Vector{T}
  foo2::Matrix{T}
end

struct Bar{T}
  bar1::Vector{T}
  bar2::T
end

@concat_struct Bax{T} begin
  Foo{T}
  Bar{T}
end

# Evaluates to:
struct Bax{T}
  foo1::Vector{T}
  foo2::Matrix{T}
  bar1::Vector{T}
  bar2::T
end

Based on the difficulties you’ve described, I eventually went with a simple composite type, though that required me doing some weird nonsense to access fields-of-components in a type-stable way. (I just messed around with stuff until constant propagation worked correctly…)

struct Bax{T}
  foo::Foo{T}
  bar::Bar{T}
end

function Base.getproperty(bax::Bax, s::Symbol)
    if hasfield(Foo, s)
        return getfield(getfield(bax, :foo), s)
    elseif hasfield(Bar, s)
        return getfield(getfield(bax, :bar), s)
    else
        return getfield(bax, s)
    end
end
Base.@pure hasfield(T, s) = s in fieldnames(T)

Not fully satisfying, but it works.

This is not a legal @pure function, as in is generic function.

3 Likes

more importantly it’s not a legal @pure function because your name isn’t Jameson.

1 Like

No problem, it seems like @pure wasn’t necessary anyway. So I’ll just take it out, easier than changing my name.

3 Likes