How do I write a @generated inner constructor?

I’m trying to create a type with an @generated inner constructor, but it seems I’m doing something wrong? Or maybe generated inner constructors are not permitted?

Here’s my attempt:

julia> immutable T
         @generated T() = :( new() )
       end

julia> T()
ERROR: UndefVarError: new not defined
Stacktrace:
 [1] T() at ./REPL[10]:2
1 Like

What are you trying to achieve?

I don’t think it’s currently easy to do. You can generate the Expr(:new) manually but maybe we can fix the generated function lowering for inner constructors…

Expr(:new) gave a segfault when I tried it. Expr(:call, :new) gave the same error message as :( new() ).

The reason I would like a generated inner constructor is that I want to do some sanity checks on type parameters, and throw an exception if there’s a problem. I had expected to be able to do this without any performance penalty, since type parameters are known constants when the method is compiled. But it turns out to be more complicated than I first thought. Instructions related to garbage collection sometimes get added even though they are not needed. (Apparently the compiler does not always realize when the garbage collected objects are constants that get folded away.) A generated function would not have this problem.

… Well, you don’t just do Expr(:new). You need to fill in the argument. Check code_lowered of a constructor to see what you should generate.

julia> type A
       A() = new()
       end

julia> @code_lowered A()
CodeInfo(:(begin
        nothing
        return $(Expr(:new, :(Main.A)))
    end))

This is also mentioned in the dev doc. Note that this is the internal AST so it can break more easily than the surface AST.

Just use dispatch to other functions that do the type checks for you. Those will receive the same optimizations for known types. Cf. https://github.com/JuliaLang/julia/blob/003a14fd215954581147df49d37e7c05fb826771/base/subarray.jl#L15

1 Like

Awesome! Thanks.

I had tried this, but it did not work when I passed the type parameters as arguments to the checking function. (Apparently the types themselves become garbage collected objects at that point.) The example you give instead infers the type parameters anew from the the types of passed-through arguments. I can see why that should work much better. I will try this!

It should also work when passing types to the checking function. The key is to put your logic into the dispatch table instead of inside a single method. Dispatching on ::Type{…} isn’t really any different than dispatching on concrete types… although invariance can make it a little harder to express.

I put the logic in a @generated checking function. Equivalent to putting it in the dispatch table in a way, but gives more flexibility. Works great! Thanks for the tip.

Is there a new way to have a generated inner constructor in Julia 1+ ? The @code_lowered on 1.2 didn’t give me any hints for how I should do it:

julia> struct Foo
           Foo() = new()
       end

julia> 

julia> @code_lowered Foo()
CodeInfo(
1 ─ %1 = %new(Main.Foo)
└──      return %1
)

which suggested I might try this, but it doesn’t work:

julia> struct Foo
           @generated Foo() = :(new(Foo))
       end

julia> Foo()
ERROR: UndefVarError: new not defined
Stacktrace:
 [1] macro expansion at ./REPL[6]:2 [inlined]
 [2] Foo() at ./REPL[6]:2
 [3] top-level scope at REPL[7]:1

FWIW my use case is that I have a complicated type with lots of parameters and ways to construct it, and I think it would be far clearer code to deal with ambiguities in the body of a generated function rather than with dispatch logic.

Ah I should have just tried the old thing, it seems to work still,

struct Foo
    @generated Foo() = :($(Expr(:new, :(Foo))))
end