Skip to content

use more precise structural inspection of types for ambiguity exclusions #327

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
68 changes: 26 additions & 42 deletions src/ambiguities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,31 @@

const ExcludeSpec = Pair{Base.PkgId,String}

strnameof(x) = string(x)
strnameof(x::Type) = string(nameof(x))

rootmodule(x) = rootmodule(parentmodule(x))
rootmodule(x::Type) = rootmodule(parentmodule(x))
rootmodule(m::Module) = Base.require(PkgId(m)) # this handles Base/Core well

normalize_exclude(x::Union{Type,Function}) =
Base.PkgId(rootmodule(x)) => join((fullname(parentmodule(x))..., strnameof(x)), ".")
normalize_exclude(::Any) = error("Only a function and type can be excluded.")
# get the Type associated with x
normalize_exclude_obj(@nospecialize x) = x isa Type ? x : typeof(x)

function normalize_exclude(@nospecialize x)
x = normalize_exclude_obj(x)
return Base.PkgId(rootmodule(x)) => join((fullname(parentmodule(x))..., string(nameof(x))), ".")
end

function getobj((pkgid, name)::ExcludeSpec)
function getexclude((pkgid, name)::ExcludeSpec)
nameparts = Symbol.(split(name, "."))
m = Base.require(pkgid)
for name in nameparts
m = getproperty(m, name)
end
return m
return normalize_exclude_obj(m)
end

function normalize_and_check_exclude(exclude::AbstractVector)
exspecs = mapfoldl(normalize_exclude, push!, exclude, init = ExcludeSpec[])
for (spec, obj) in zip(exspecs, exclude)
if getobj(spec) !== obj
error("Name `$(spec[2])` is resolved to a different object.")
exspecs = ExcludeSpec[normalize_exclude(exspec) for exspec in exclude]
for (i, exspec) in enumerate(exspecs)
if getexclude(exspec) != normalize_exclude_obj(exclude[i])
error("Name `$(exspec[2])` is resolved to a different object.")
end
end
return exspecs::Vector{ExcludeSpec}
Expand Down Expand Up @@ -154,35 +155,18 @@
return "Base.PkgId(Base.UUID($(repr(uuid.value))), $(repr(name)))"
end

struct _NoValue end

function getobj(m::Method)
signature = Base.unwrap_unionall(m.sig)
ty = if is_kwcall(signature)
signature.parameters[3]
else
signature.parameters[1]
# try to extract the called function, or nothing if it is hard to analyze
function trygetft(m::Method)
sig = Base.unwrap_unionall(m.sig)::DataType
ft = sig.parameters[is_kwcall(sig) ? 3 : 1]
ft = Base.unwrap_unionall(ft)
if ft isa DataType && ft.name === Type.body.name
ft = Base.unwrap_unionall(ft.parameters[1])
end
ty = Base.unwrap_unionall(ty)
if ty <: Function
try
return ty.instance # this should work for functions
catch
end
end
try
if ty.name.wrapper === Type
return ty.parameters[1]
else
return ty.name.wrapper
end
catch err
@error(
"Failed to obtain a function from `Method`.",
exception = (err, catch_backtrace())
)
if ft isa DataType
return ft.name.wrapper
end
return _NoValue()
return nothing # cannot exclude signatures with Union

Check warning on line 169 in src/ambiguities.jl

View check run for this annotation

Codecov / codecov/patch

src/ambiguities.jl#L169

Added line #L169 was not covered by tests
end

function test_ambiguities_impl(
Expand All @@ -196,9 +180,9 @@
ambiguities = detect_ambiguities(modules...; options...)

if !isempty(exspecs)
exclude_objs = getobj.(exspecs)
exclude_ft = Any[getexclude(spec) for spec in exspecs] # vector of Type objects
ambiguities = filter(ambiguities) do (m1, m2)
getobj(m1) ∉ exclude_objs && getobj(m2) ∉ exclude_objs
trygetft(m1) ∉ exclude_ft && trygetft(m2) ∉ exclude_ft
end
end

Expand Down
2 changes: 1 addition & 1 deletion src/piracies.jl
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ function is_pirate(meth::Method; treat_as_own = Union{Function,Type}[])
signature = Base.unwrap_unionall(meth.sig)

function_type_index = 1
if is_kwcall(signature)
if is_kwcall(meth.sig)
# kwcall is a special case, since it is not a real function
# but a wrapper around a function, the third parameter is the original
# function, its positional arguments follow.
Expand Down
5 changes: 3 additions & 2 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ function checked_repr(obj)
return code
end

function is_kwcall(signature::DataType)
function is_kwcall(signature::Type)
@static if VERSION < v"1.9"
signature = Base.unwrap_unionall(signature)::DataType
try
length(signature.parameters) >= 3 || return false
signature <: Tuple{Function,Any,Any,Vararg} || return false
Expand All @@ -69,6 +70,6 @@ function is_kwcall(signature::DataType)
return false
end
else
return signature.parameters[1] === typeof(Core.kwcall)
return signature <: Tuple{typeof(Core.kwcall), Any, Any, Vararg}
end
end
5 changes: 5 additions & 0 deletions test/test_ambiguities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ include("preamble.jl")
if broken
@test_broken num_ambiguities_ == num_ambiguities
else
if num_ambiguities_ != num_ambiguities
@show exclude
println(strout)
println(strerr)
end
Comment on lines +27 to +31
Copy link
Member

Choose a reason for hiding this comment

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

How is this related to the rest of the changes in here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just debugging for the relevant tests if they change reporting

@test num_ambiguities_ == num_ambiguities
end
end
Expand Down
17 changes: 11 additions & 6 deletions test/test_exclude.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module TestExclude

include("preamble.jl")
using Base: PkgId
using Aqua: getobj, normalize_exclude, normalize_and_check_exclude, rootmodule, reprexclude
using Aqua: getexclude, normalize_exclude, normalize_exclude_obj, normalize_and_check_exclude, rootmodule, reprexclude

@assert parentmodule(Tuple) === Core
@assert parentmodule(foldl) === Base
Expand All @@ -11,27 +11,32 @@ using Aqua: getobj, normalize_exclude, normalize_and_check_exclude, rootmodule,
@assert rootmodule(Broadcast.Broadcasted) === Base

@testset "roundtrip" begin
@testset for x in [
@testset for x in Any[
foldl
Some
Tuple
Broadcast.Broadcasted
nothing
Any
]
@test getobj(normalize_exclude(x)) == x
@test getexclude(normalize_exclude(x)) === normalize_exclude_obj(x)
end
@test_throws ErrorException normalize_and_check_exclude(Any[Pair{Int}])

@testset "$(repr(last(spec)))" for spec in [
(PkgId(Base) => "Base.foldl")
(PkgId(Base) => "Base.#foldl")
(PkgId(Base) => "Base.Some")
(PkgId(Core) => "Core.Tuple")
(PkgId(Base) => "Base.Broadcast.Broadcasted")
(PkgId(Core) => "Core.Nothing")
(PkgId(Core) => "Core.Any")
]
@test normalize_exclude(getobj(spec)) === spec
@test normalize_exclude(getexclude(spec)) === spec
end
end

@testset "normalize_and_check_exclude" begin
@testset "$i" for (i, exclude) in enumerate([[foldl], [foldl, Some], [foldl, Tuple]])
@testset "$i" for (i, exclude) in enumerate([Any[foldl], Any[foldl, Some], Any[foldl, Tuple]])
local specs
@test (specs = normalize_and_check_exclude(exclude)) isa Vector
@test Base.include_string(@__MODULE__, reprexclude(specs)) == specs
Expand Down
20 changes: 0 additions & 20 deletions test/test_getobj.jl

This file was deleted.

Loading