Semantics of :: in return type vs. argument type annotations

An issue with return statements is that they are not always needed, and I don’t see how a macro could easily determine all possible return values of a function when not all of them are associated with an explicit return. For a macro-based approach, I’d rather use an approach in which a function definition like this:

@returnassert function foo(x, y) :: Float64
    if (rand() > 0.5)
        return x + y
    end

    # no explicit return here
    x - y
end

is transformed into something like this:

function _inner_foo(x, y)
    if (rand() > 0.5)
        return x + y
    end

    # no explicit return here
    x - y
end

foo(x,y) = _inner_foo(x,y) :: Float64

A proof-of-concept should not be too hard to write. The following implementation does not handle keyword arguments but should otherwise more or less get the job done:

PoC code
using MacroTools

macro returnassert(defun)
    inner = splitdef(defun)

    name   = inner[:name]
    args   = inner[:args]
    rtype  = inner[:rtype]

    inner[:name] = gensym(name)
    delete!(inner, :rtype)

    wrapper = Dict(
        :name => name,
        :args => args,
        :kwargs => inner[:kwargs],
    )

    wrapper[:body] = quote
        $(inner[:name])($(args...)) :: $rtype
    end

    quote
        $(combinedef(inner))
        $(combinedef(wrapper))
    end |> esc
end
julia> @returnassert function foo(x, y) :: Float64
           if (rand() > 0.5)
               return x + y
           end
       
           # no explicit return here
           x - y
       end
foo (generic function with 1 method)

julia> foo(1.0, 2)
3.0

julia> foo(1, 2)
ERROR: TypeError: in typeassert, expected Float64, got a value of type Int64
Stacktrace:
 [1] foo(x::Int64, y::Int64)
   @ Main ./REPL[2]:18
 [2] top-level scope
   @ REPL[6]:1
2 Likes