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