Skip to content

Commit

Permalink
Add test macro for relaxed inference of small unions
Browse files Browse the repository at this point in the history
  • Loading branch information
haampie committed Jun 9, 2018
1 parent 2a45839 commit a1fdab0
Showing 1 changed file with 65 additions and 22 deletions.
87 changes: 65 additions & 22 deletions stdlib/Test/src/Test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export @test, @test_throws, @test_broken, @test_skip,

export @testset
# Legacy approximate testing functions, yet to be included
export @inferred
export @inferred, @weakly_inferred
export detect_ambiguities, detect_unbound_args
export GenericString, GenericSet, GenericDict, GenericArray
export guardsrand, TestSetException
Expand All @@ -30,6 +30,7 @@ import Distributed: myid

using Random: srand, AbstractRNG, GLOBAL_RNG
using InteractiveUtils: gen_call_with_extracted_types
using Core.Compiler: typesubtract

#-----------------------------------------------------------------------

Expand Down Expand Up @@ -1257,6 +1258,29 @@ function get_testset_depth()
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)
Expand Down Expand Up @@ -1291,29 +1315,9 @@ julia> @inferred max(1,2)
```
"""
macro inferred(ex)
if Meta.isexpr(ex, :ref)
ex = Expr(:call, :getindex, ex.args...)
end
Meta.isexpr(ex, :call)|| error("@inferred requires a call expression")

Base.remove_linenums!(quote
let
$(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)
$(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])")
Expand All @@ -1322,6 +1326,45 @@ macro inferred(ex)
end)
end

"""
@weakly_inferred f(x)
Relaxes [`@inferred`](@ref) for functions that return small unions in two ways:
1. When `f(x)` is `missing` or `nothing`, the test passes if `Missing` and `Nothing` are
respectively subtypes of the inferred type;
2. Otherwise the test passes provided that the value type equals the inferred type modulo
`Nothing` and `Missing`.
```jldoctest; setup = :(using InteractiveUtils), filter = r"begin\\n(.|\\n)*end"
julia> produce(a) = a < 18 ? nothing : a
julia> @inferred produce(10)
ERROR: return type Nothing does not match inferred return type Union{Nothing, Int64}
Stacktrace:
[...]
julia> @weakly_inferred produce(10)
julia> @weakly_inferred produce(20)
20
```
"""
macro weakly_inferred(ex)
Base.remove_linenums!(quote
let
$(call_function_and_infer_type(ex, __module__))
@assert length(inftypes) == 1
inftype = inftypes[1]
rettype = isa(result, Type) ? Type{result} : typeof(result)
inferred = rettype == Nothing && (Nothing <: inftype) ||
rettype == Missing && (Missing <: inftype) ||
rettype == typesubtract(inftype, Union{Nothing, Missing})
inferred || error("return type $rettype does not weakly match inferred return type $(inftype)")
result
end
end)
end

"""
detect_ambiguities(mod1, mod2...; imported=false, recursive=false, ambiguous_bottom=false)
Expand Down

0 comments on commit a1fdab0

Please sign in to comment.