Skip to content

Fix handling of allow_bottom_tparams in isambiguous #20982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -941,13 +941,41 @@ function method_exists(f::ANY, t::ANY)
typemax(UInt)) != 0
end

"""
isambiguous(m1, m2, [allow_bottom_tparams=true]) -> Bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated aside, but I think we should gradually aim to get away from using square brackets to denote optional arguments and uniformly use the julia syntax convention for them in docstrings


Determine whether two methods `m1` and `m2` (typically of the same
function) are ambiguous. This test is performed in the context of
other methods of the same function; in isolation, `m1` and `m2` might
be ambiguous, but if a third method resolving the ambiguity has been
defined, this returns `false`.

For parametric types, `allow_bottom_tparams` controls whether
`Union{}` is considered a valid intersection of type parameters. For example:
```jldoctest
julia> foo(x::Complex{<:Integer}) = 1
foo (generic function with 1 method)

julia> foo(x::Complex{<:Rational}) = 2
foo (generic function with 2 methods)

julia> m1, m2 = collect(methods(foo));

julia> typeintersect(m1.sig, m2.sig)
Tuple{#foo,Complex{Union{}}}

julia> Base.isambiguous(m1, m2, true)
true

julia> Base.isambiguous(m1, m2, false)
false
```
"""
function isambiguous(m1::Method, m2::Method, allow_bottom_tparams::Bool=true)
ti = typeintersect(m1.sig, m2.sig)
ti === Bottom && return false
if !allow_bottom_tparams
(_, env) = ccall(:jl_match_method, Ref{SimpleVector}, (Any, Any),
ti, m1.sig)
any(x->x === Bottom, env) && return false
has_bottom_parameter(ti) && return false
end
ml = _methods_by_ftype(ti, -1, typemax(UInt))
isempty(ml) && return true
Expand All @@ -959,6 +987,23 @@ function isambiguous(m1::Method, m2::Method, allow_bottom_tparams::Bool=true)
return true
end

"""
has_bottom_parameter(t) -> Bool

Determine whether `t` is a Type for which one or more of its parameters is `Union{}`.
"""
function has_bottom_parameter(t::Type)
ret = false
for p in t.parameters
ret |= (p == Bottom) || has_bottom_parameter(p)
end
ret
end
has_bottom_parameter(t::UnionAll) = has_bottom_parameter(unwrap_unionall(t))
has_bottom_parameter(t::Union) = has_bottom_parameter(t.a) & has_bottom_parameter(t.b)
has_bottom_parameter(t::TypeVar) = has_bottom_parameter(t.ub)
has_bottom_parameter(::Any) = false

min_world(m::Method) = reinterpret(UInt, m.min_world)
max_world(m::Method) = reinterpret(UInt, m.max_world)
min_world(m::Core.MethodInstance) = reinterpret(UInt, m.min_world)
Expand Down
6 changes: 5 additions & 1 deletion base/test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1117,12 +1117,16 @@ function test_approx_eq_modphase{S<:Real,T<:Real}(
end

"""
detect_ambiguities(mod1, mod2...; imported=false)
detect_ambiguities(mod1, mod2...; imported=false, allow_bottom=true)

Returns a vector of `(Method,Method)` pairs of ambiguous methods
defined in the specified modules. Use `imported=true` if you wish to
also test functions that were imported into these modules from
elsewhere.

`allow_bottom` controls whether ambiguities triggered only by
`Union{}` type parameters are included; in most cases you probably
want to set this to `false`. See [`Base.isambiguous`](@ref).
"""
function detect_ambiguities(mods...; imported::Bool=false, allow_bottom::Bool=true)
function sortdefs(m1, m2)
Expand Down
2 changes: 1 addition & 1 deletion test/ambiguous.jl
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ ambs = detect_ambiguities(Ambig5)

# Test that Core and Base are free of ambiguities
# TODO jb/subtype: we now detect a lot more
@test_broken detect_ambiguities(Core, Base; imported=true) == []
@test_broken detect_ambiguities(Core, Base; imported=true, allow_bottom=false) == []
# not using isempty so this prints more information when it fails

amb_1(::Int8, ::Int) = 1
Expand Down