Skip to content

Commit

Permalink
Remove @weakly_inferred and combine it with @inferred
Browse files Browse the repository at this point in the history
  • Loading branch information
haampie committed Jun 10, 2018
1 parent 870fbbb commit 2cc57f8
Showing 1 changed file with 56 additions and 73 deletions.
129 changes: 56 additions & 73 deletions stdlib/Test/src/Test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1259,108 +1259,91 @@ end

_args_and_call(args...; kwargs...) = (args[1:end-1], kwargs, args[end](args[1:end-1]...; kwargs...))

function call_function_and_infer_type(ex, context)
if Meta.isexpr(ex, :ref)
ex = Expr(:call, :getindex, ex.args...)
end
Meta.isexpr(ex, :call) || error("inference test requires a call expression")
if any(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex.args)
# Has keywords
args, kwargs = gensym(), gensym()
quote
$(esc(args)), $(esc(kwargs)), result = $(esc(Expr(:call, _args_and_call, ex.args[2:end]..., ex.args[1])))
inftypes = $(gen_call_with_extracted_types(context, Base.return_types, :($(ex.args[1])($(args)...; $(kwargs)...))))
end
else
# No keywords
quote
args = ($([esc(ex.args[i]) for i = 2:length(ex.args)]...),)
result = $(esc(ex.args[1]))(args...)
inftypes = Base.return_types($(esc(ex.args[1])), Base.typesof(args...))
end
end
end

"""
@inferred f(x)
@inferred f(x) [AllowedType]
Tests that the call expression `f(x)` returns a value of the same type inferred by the
compiler. It is useful to check for type stability.
Tests that the call expression `f(x)` returns a value of the same type
inferred by the compiler. It is useful to check for type stability.
`f(x)` can be any call expression. Returns the result of `f(x)` if the types match, and an
`Error` `Result` if it finds different types.
`f(x)` can be any call expression.
Returns the result of `f(x)` if the types match,
and an `Error` `Result` if it finds different types.
Optionally, `AllowedType` relaxes the test, by making it pass when either the type of `f(x)`
matches the inferred type modulo `AllowedType`, or when the return type is a subtype of
`AllowedType`. This is useful when testing type stability of functions returning a small
union such as `Union{Nothing, T}` or `Union{Missing, T}`.
```jldoctest; setup = :(using InteractiveUtils), filter = r"begin\\n(.|\\n)*end"
julia> f(a,b,c) = b > 1 ? 1 : 1.0
julia> f(a) = a > 1 ? 1 : 1.0
f (generic function with 1 method)
julia> typeof(f(1,2,3))
julia> typeof(f(2))
Int64
julia> @code_warntype f(1,2,3)
Body::UNION{FLOAT64, INT64}
1 1 ─ %1 = Base.slt_int(1, %%b)::Bool
julia> @code_warntype f(2)
Body::Union{Float64, Int64}
1 1 ─ %1 = Base.slt_int(1, %%a)::Bool
└── goto 3 if not %1
2 ─ return 1
3 ─ return 1.0
julia> @inferred f(1,2,3)
julia> @inferred f(2)
ERROR: return type Int64 does not match inferred return type Union{Float64, Int64}
Stacktrace:
[...]
julia> @inferred max(1,2)
2
```
"""
macro inferred(ex)
Base.remove_linenums!(quote
let
$(call_function_and_infer_type(ex, __module__))
@assert length(inftypes) == 1
rettype = isa(result, Type) ? Type{result} : typeof(result)
rettype == inftypes[1] || error("return type $rettype does not match inferred return type $(inftypes[1])")
result
end
end)
end

"""
@weakly_inferred f(x)
Relaxes [`@inferred`](@ref) for functions that return small unions in two ways:
1. The test passes when `f(x)` returns `missing` or `nothing`.
2. Otherwise it passes provided that the value type equals the inferred type modulo
`Nothing` and `Missing`.
julia> g(a) = a < 10 ? missing : 1.0
g (generic function with 1 method)
```jldoctest; setup = :(using InteractiveUtils), filter = r"begin\\n(.|\\n)*end"
julia> f(x) = x < 18 ? nothing : x
julia> @inferred f(10)
ERROR: return type Nothing does not match inferred return type Union{Nothing, Int64}
Stacktrace:
julia> @inferred g(20)
ERROR: return type Float64 does not match inferred return type Union{Missing, Float64}
[...]
julia> @weakly_inferred f(10)
julia> @inferred g(20) Missing
1.0
julia> h(a) = a < 10 ? missing : f(a)
h (generic function with 1 method)
julia> @weakly_inferred f(20)
20
julia> @inferred h(20) Missing
ERROR: return type Int64 does not match inferred return type Union{Missing, Float64, Int64}
[...]
```
"""
macro weakly_inferred(ex)
Base.remove_linenums!(quote
macro inferred(ex, allow = :(Union{}))
if Meta.isexpr(ex, :ref)
ex = Expr(:call, :getindex, ex.args...)
end
Meta.isexpr(ex, :call) || throw(ArgumentError("@inferred requires a call expression"))

quote
let
$(call_function_and_infer_type(ex, __module__))
allow = $(esc(allow))
allow isa Type || throw(ArgumentError("@inferred requires a type as second argument"))
$(if any(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex.args)
# Has keywords
args = gensym()
kwargs = gensym()
quote
$(esc(args)), $(esc(kwargs)), result = $(esc(Expr(:call, _args_and_call, ex.args[2:end]..., ex.args[1])))
inftypes = $(gen_call_with_extracted_types(__module__, Base.return_types, :($(ex.args[1])($(args)...; $(kwargs)...))))
end
else
# No keywords
quote
args = ($([esc(ex.args[i]) for i = 2:length(ex.args)]...),)
result = $(esc(ex.args[1]))(args...)
inftypes = Base.return_types($(esc(ex.args[1])), Base.typesof(args...))
end
end)
@assert length(inftypes) == 1
inftype = inftypes[1]
rettype = isa(result, Type) ? Type{result} : typeof(result)
inferred = rettype == Nothing || rettype == Missing ||
rettype == typesubtract(inftype, Union{Nothing, Missing})
inferred || error("return type $rettype does not weakly match inferred return type $(inftype)")
rettype = result isa Type ? Type{result} : typeof(result)
rettype <: allow || rettype == typesubtract(inftypes[1], allow) || error("return type $rettype does not match inferred return type $(inftypes[1])")
result
end
end)
end
end

"""
Expand Down

0 comments on commit 2cc57f8

Please sign in to comment.