# Default constructor not generated for struct created by \`eval\`

**URL:** https://discourse.julialang.org/t/default-constructor-not-generated-for-struct-created-by-eval/123602
**Category:** General Usage
**Tags:** question
**Created:** [December 9, 2024, 4:46am UTC](https://discourse.julialang.org/t/default-constructor-not-generated-for-struct-created-by-eval/123602 "2024-12-09T04:46:49Z")
**Posts on this page:** 6
**Page:** 1

<div class="post-metadata">

### Author: ![cuihantao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cuihantao/32/8783_2.png) [@cuihantao](https://discourse.julialang.org/u/cuihantao)
#### Post date: [December 9, 2024, 4:46am UTC](https://discourse.julialang.org/t/default-constructor-not-generated-for-struct-created-by-eval/123602/1 "2024-12-09T04:46:49Z")

</div>

I’m trying to write a function to “vectorize” a struct. A plain struct would be defined like:

```julia
struct Bus{T}
    vm::T
end

```

And I want the output to be:

```julia
struct BusVec{T}
    vm::Vector{T}
end

```

A function is implemented below by parsing the defined `Bus` to get the field names and types. A `struct` expression will be created and `eval`ed.

The problem is that the newly created struct does not have any constructor.

Copy-and-paste MWE:

```julia

"""
This is the MWE of some function to derive a new type from a defined type.
"""
function generate_vector_type(::Type{T}) where T
    struct_name = Symbol(nameof(T), "Vec")
    fields = fieldnames(T)
    
    # Get field information from the original type definition
    type_def = Base.unwrap_unionall(T)
    field_types = type_def.types
    
    # Create vectorized field expressions
    field_exprs = [:($(fields[i])::Vector{$(field_types[i])}) for i in eachindex(fields)]
    
    # Get type parameters, ignore supertype for now
    type_params = if T isa UnionAll
        [T.var.name]
    else
        T.parameters
    end
    
    # Construct the type expression with parameters
    param_expr = if !isempty(type_params)
        Expr(:curly, struct_name, type_params...)
    else
        struct_name
    end

    # Generate struct definition
    struct_expr = Expr(:struct, true,
        param_expr,
        Expr(:block, field_exprs...)
    )
    return struct_expr

end

struct Bus{T}
    vm::T
end

expr = generate_vector_type(Bus)
@show expr

eval(expr)

methods(BusVec)

```

Ouputs

```julia

expr = :(mutable struct BusVec{T}
      vm::Vector{T}
  end)
# 0 methods for type constructor

```

I then copied `expr`, renamed the struct, and run it in the same REPL, the default constructor is generated.

```julia
mutable struct BusVec2{T}
      vm::Vector{T}
  end

methods(BusVec2)

```

This gives

```julia
# 1 method for type constructor:
BusVec2(vm::Vector{T}) where T in Main at In[24]:2

```

I’m suspecting rules/scoping issues related to `eval` but couldn’t figure it out after a day. Any suggestions are appreciated!

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [December 9, 2024, 5:05am UTC](https://discourse.julialang.org/t/default-constructor-not-generated-for-struct-created-by-eval/123602/2 "2024-12-09T05:05:53Z")

</div>

`dump(expr)` exposes this discrepancy in `vm::Vector{T}` that doesn’t `print` differently:

```julia
        1: Expr
          head: Symbol ::
          args: Array{Any}((2,))
            1: Symbol vm
            2: Expr
              head: Symbol curly
              args: Array{Any}((2,))
                1: Symbol Vector
                2: TypeVar

```

That `2:TypeVar` should be a `2: Symbol T`. A quick fix to `field_types[i].name` makes the expected default method show up, but I wonder if there are nicer methods, even internal ones, for all this.

---

<div class="post-metadata">

### Author: ![cuihantao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cuihantao/32/8783_2.png) [@cuihantao](https://discourse.julialang.org/u/cuihantao)
#### Post date: [December 9, 2024, 2:51pm UTC](https://discourse.julialang.org/t/default-constructor-not-generated-for-struct-created-by-eval/123602/3 "2024-12-09T14:51:59Z")

</div>

> [@cuihantao](#):
>
> ```julia
> field_types = type_def.types
> 
> ```

Thank you very much for your reply!

I never thought about that `curly`’s args should only be `Symbol`s. `TypeVar` is a non-functional argument to `{}`.

What would be a permanet fix? Calling `Symbol` is not, because there are edge cases like imported types and Union types…

I wonder what gets created out of my wrong expression, and why it didn’t fail loudly…

---

<div class="post-metadata">

### Author: ![cuihantao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cuihantao/32/8783_2.png) [@cuihantao](https://discourse.julialang.org/u/cuihantao)
#### Post date: [December 9, 2024, 4:13pm UTC](https://discourse.julialang.org/t/default-constructor-not-generated-for-struct-created-by-eval/123602/4 "2024-12-09T16:13:28Z")

</div>

This is what I’m currently doing:

```julia
    field_exprs = [
        begin
            field_type = field_types[i]
            type_expr = if field_type isa Union
                # Handle Union types using Base.uniontypes
                union_types = map(t -> Symbol(split(string(t), ".")[end]), Base.uniontypes(field_type))
                Expr(:curly, :Union, union_types...)
            else
                # Handle simple types, stripping module qualification
                Symbol(split(string(field_type), ".")[end])
            end
            :($(fields[i])::Vector{$type_expr})
        end for i in eachindex(fields)
    ]

```

This snippet reconstructs the symbols for the type of the fields. It’s handling qualified name by stripping the module name and handling Union types.

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [December 9, 2024, 6:34pm UTC](https://discourse.julialang.org/t/default-constructor-not-generated-for-struct-created-by-eval/123602/5 "2024-12-09T18:34:45Z")

</div>

> [@cuihantao](#):
>
> I wonder what gets created out of my wrong expression, and why it didn’t fail loudly

The root of the problem is that it’s a bit different from the normal metaprogramming of `Expr` to `Expr`. Instead, you’re processing to and taking apart an atypical `DataType`, and `.types` contains `TypeVar`s, not `Symbol`s, for type parameters.

```julia
julia> Base.unwrap_unionall(Bus).types
svec(T)

julia> x = Base.unwrap_unionall(Bus).types[1]
T

julia> x |> dump
TypeVar
  name: Symbol T
  lb: Union{}
  ub: Any

```

`TypeVar`s have extra information in the type bounds. You normally never get access to `TypeVar`s, but you have to be extra careful when you do.

The first reason why it doesn’t fail loudly is that `Expr` trees can contain any objects, not just the `Symbol`s, `Expr`s, and a few literals parsed from source code. The correct type definition is obviously parseable from source code, so interpolating something else like `TypeVar` reasonably risks a difference.

The second reason it doesn’t fail loudly is because the bad expression evaluates to the same kind of `DataType` that `unwrap_unionall` makes:

```julia
julia> :(Vector{$x}) |> eval
Array{T, 1}

julia> :(Vector{$x}) |> eval |> typeof
DataType

```

Such a `DataType` is allowed to exist for type processing, and that’s what you annotated the field with.

The third and final reason it doesn’t fail loudly is that Julia doesn’t know to check for such atypical `DataType`s. If you didn’t have a type parameter, it does complain for another reason:

```julia
julia> :(struct Y y::$x end) |> eval
ERROR: ArgumentError: method definition for Y at REPL[8]:1 has free type variables

```

but once you have enough type parameters, it just trusts that you didn’t do something unusual.

Although the type definition or a concrete type does not fail, instantiation does because the type’s parameter cannot be associated with the inserted `TypeVar`:

```julia
julia> BusVec{Int} |> dump
BusVec{Int64} <: Any
  vm::Array{T, 1}

julia> BusVec{Int}([1])
ERROR: UndefVarError: `T` not defined in static parameter matching
Suggestion: run Test.detect_unbound_args to detect method arguments that do not fully constrain a type parameter.

```

Since `Base.unwrap_unionall` is already an internal function, I think getting the symbol from the internal (assume if not documented or only documented in experimental or developer documentation) `.name` is just as okay for working with `TypeVar`s. I should clarify my earlier comment that it’s preferable to work with a well-maintained library that exposes public API for similar purposes because someone handles the internals for you and everyone else, and if you must dig into internals it’s better to use ones that seem designed to change less.

---

<div class="post-metadata">

### Author: ![cuihantao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cuihantao/32/8783_2.png) [@cuihantao](https://discourse.julialang.org/u/cuihantao)
#### Post date: [December 10, 2024, 3:01am UTC](https://discourse.julialang.org/t/default-constructor-not-generated-for-struct-created-by-eval/123602/6 "2024-12-10T03:01:58Z")

</div>

Thank you very much for your thoughtful response! I now understand the internals of the type system a little better.

I guess an alternative is to implement a macro which does `Expr` to `Expr` processing. It will look like

```julia
@make_vec struct Bus{T}
    vm::T
end

```

But in my actual code, the macro was getting to long. I’ll see if I can find an existing package or make one my own.
