Given a Julia function `f` and its arguments `agrs` return typed Julia source code via a macro

Is there a macro for that? Or is it possible to write one?

It’s always easier to understand via an example:

function f(x,y)
    a = sqrt(x)
    b = x*y
    return a + b

Macro call:

@get_typed_source_code f(1,2)
function f(x::Int64, y::Int64)::Float64
    a::Float64 = sqrt(x::Int64)
    b::Int64 = x::Int64 * y::Int64
    return a::Float64 + b::Int64

Eg @code_typed?

Kind of, but not that low level. More like in the example above so that one can copy-past the generated julia source code

It may be doable, but quite a few passes are done before the compiler types some code, so it would have to go back somehow to the original form.

I am curious about the use case.

Just exploring the language :slight_smile:
For beginners it’s much easier to read and explore Julia source code instead of the lowered Julia code. And sometimes one might want fully typed code.
(And my boss is used to statically typed languages and what to type everything)

1 Like

I think one might start in macros.jl with this piece of code

function gen_call_with_extracted_types(__module__, fcn, ex0)
    if isa(ex0, Expr)
        if any(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex0.args)
            return quote
                local arg1 = $(esc(ex0.args[1]))
                local args, kwargs = $separate_kwargs($(map(esc, ex0.args[2:end])...))
                       Tuple{typeof(kwargs), Core.Typeof(arg1), map(Core.Typeof, args)...})
        elseif ex0.head === :call
            return Expr(:call, fcn, esc(ex0.args[1]),
                        Expr(:call, typesof, map(esc, ex0.args[2:end])...))
        elseif ex0.head === :(=) && length(ex0.args) == 2
            lhs, rhs = ex0.args
            if isa(lhs, Expr)
                if lhs.head === :(.)
                    return Expr(:call, fcn, Base.setproperty!,
                                Expr(:call, typesof, map(esc, lhs.args)..., esc(rhs)))
                elseif lhs.head === :ref
                    return Expr(:call, fcn, Base.setindex!,
                                Expr(:call, typesof, esc(lhs.args[1]), esc(rhs), map(esc, lhs.args[2:end])...))
        elseif ex0.head === :vcat || ex0.head === :typed_vcat
            if ex0.head === :vcat
                f, hf = Base.vcat, Base.hvcat
                args = ex0.args
                f, hf = Base.typed_vcat, Base.typed_hvcat
                args = ex0.args[2:end]
            if any(a->isa(a,Expr) && a.head === :row, args)
                rows = Any[ (isa(x,Expr) && x.head === :row ? x.args : Any[x]) for x in args ]
                lens = map(length, rows)
                return Expr(:call, fcn, hf,
                            Expr(:call, typesof,
                                 (ex0.head === :vcat ? [] : Any[esc(ex0.args[1])])...,
                                 Expr(:tuple, lens...),
                                 map(esc, vcat(rows...))...))
                return Expr(:call, fcn, f,
                            Expr(:call, typesof, map(esc, ex0.args)...))
            for (head, f) in (:ref => Base.getindex, :hcat => Base.hcat, :(.) => Base.getproperty, :vect => Base.vect, Symbol("'") => Base.adjoint, :typed_hcat => Base.typed_hcat, :string => string)
                if ex0.head === head
                    return Expr(:call, fcn, f,
                                Expr(:call, typesof, map(esc, ex0.args)...))
    if isa(ex0, Expr) && ex0.head === :macrocall # Make @edit @time 1+2 edit the macro by using the types of the *expressions*
        return Expr(:call, fcn, esc(ex0.args[1]), Tuple{#=__source__=#LineNumberNode, #=__module__=#Module, Any[ Core.Typeof(a) for a in ex0.args[3:end] ]...})

    ex = Meta.lower(__module__, ex0)
    if !isa(ex, Expr)
        return Expr(:call, :error, "expression is not a function call or symbol")

    exret = Expr(:none)
    if ex.head === :call
        if any(e->(isa(e, Expr) && e.head === :(...)), ex0.args) &&
            (ex.args[1] === GlobalRef(Core,:_apply) ||
             ex.args[1] === GlobalRef(Base,:_apply))
            # check for splatting
            exret = Expr(:call, ex.args[1], fcn,
                        Expr(:tuple, esc(ex.args[2]),
                            Expr(:call, typesof, map(esc, ex.args[3:end])...)))
            exret = Expr(:call, fcn, esc(ex.args[1]),
                         Expr(:call, typesof, map(esc, ex.args[2:end])...))
    if ex.head === :thunk || exret.head === :none
        exret = Expr(:call, :error, "expression is not a function call, "
                                  * "or is too complex for @$fcn to analyze; "
                                  * "break it down to simpler parts if possible")
    return exret

Unfortunately I’m not able to understand it yet :joy:

No, this is currently not possible, see:

Edit: this is only regarding extracting the original source code, without any type information.

1 Like

you can use @edit to have the source code pop up in a text editor.

Who do I use it?

julia> using Revise

julia> Revise.@edit
ERROR: LoadError: UndefVarError: @edit not defined
in expression starting at REPL[53]:1

@edit is not in Revise, it’s in InteractiveUtils, so it should already be imported in the REPL.


Make sure you know how to do julia> ?@edit in the REPL to see the documentation. In this case you write

julia> @edit println("hey")

But how would I use this to get typed Julia source code?

Oh, sorry, I might have misunderstood your original question. In Julia, expressions don’t have any types. The way the compiler works, your code is first lowered into an intermediate representation (IR), which turns loops and conditionals into gotos, handles fused broadcasting, etc. This is also where tools like Cassette and IRTools operate at. Type inference is then done only on this IR code and you can see what this results in with @code_typed. This means that you can’t really ask Julia for typed Julia code, because that’s not how the compiler works and may not always be well-posed. I would just try to experiment with @code_typed a bit, since it can be really useful for getting a feeling for where type instabilities occur.


It’s unrelated. And the link in Given a Julia function `f` and its arguments `agrs` return typed Julia source code via a macro - #7 by simeonschaub isn’t related either.

Just don’t…


Haha :joy:

OP I think the real solution here is to accept that Julia has dynamic elements, in that you can write duck-typed code at a high level and it will still be performant. I would suggest do @code_warntype a bunch and fixing you see Any pop up. It’s not perfect but it’s more in the spirit of writing “julian” code where you don’t nail down all the types all the time.

1 Like