A macro for removing calls to Base.convert: @noconvert

From the prior discussion on type annotations I was wondering what a macro that removed all calls to Base.convert would look like.

Here’s my prototype via Cassette.jl:

using Cassette
Cassette.@context NoConvertCtx;
Cassette.overdub(context::NoConvertCtx, ::typeof(Base.convert), _, x) = x
macro noconvert(e)
    e.head == :function || error("Expression for @noconvert must be a full `function` definition")
    s = gensym("noconvert")
    return_type = Any
    if e.args[1].head == :(::)
        return_type = e.args[1].args[2] 
        e.args[1] = e.args[1].args[1]
    end
    if e.args[1].head == :call
        fname = e.args[1].args[1]
        fargs = e.args[1].args[2:end]
    end
    e2 = deepcopy(e)
    e2.args[2] = :(Cassette.overdub(Ctx(), $s)::$return_type)
    for arg in fargs
        push!(e2.args[2].args[1].args, arg isa Expr ? arg.args[1] : arg)
    end
    if e.args[1].head == :call
        e.args[1].args[1] = s
    end
    quote
        let
            global $fname
            $e
            $e2
        end
    end
end
julia> function foo(x::Number)
           _x::Int = x
           return _x
       end
foo (generic function with 2 methods)

julia> foo(1)
1

julia> foo(2.0)
2

julia> foo(2.5)
ERROR: InexactError: Int64(2.5)

julia> @noconvert function foo(x::Number)
           _x::Int = x
           return _x
       end
foo (generic function with 2 methods)

julia> foo(3)
3

julia> foo(4.0)
ERROR: TypeError: in typeassert, expected Int64, got a value of type Float64

julia> foo(4.5)
ERROR: TypeError: in typeassert, expected Int64, got a value of type Float64

The calls to foo(4.0) and foo(4.5) both fail since the argument is a Float64 and conversion to Int is no longer done.

Can you improve upon this macro? We need to generalize this to other function definition forms or perhaps even other code blocks. Cassette.jl is also a bit overkill I think, but the interface is so easy.

Also, I need to generalize for more arguments.

5 Likes

here is a function for returning x only if the x is of a given type

function cvt(::Type{T}, x) where T
      if isa(x, T)
           x
      else
           throw(DomainError("expected x::$T, received $x::$(typeof(x))"))
      end
end

This implementation below work as well and there is not a need for a custom error.

julia> function cvt(::Type{T}, x) where T
           return x::T
       end
cvt (generic function with 1 method)

julia> cvt(Int, 5)
5

julia> cvt(Float64, 5)
ERROR: TypeError: in typeassert, expected Float64, got a value of type Int64
Stacktrace:
 [1] cvt(::Type{Float64}, x::Int64)
   @ Main .\REPL[30]:2
 [2] top-level scope
   @ REPL[32]:1