Skip to content

Add a few new reflection methods (after type system merge) #20006

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 5 commits into from
Feb 1, 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
9 changes: 9 additions & 0 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ function unwrapva(t::ANY)
isvarargtype(t2) ? t2.parameters[1] : t
end

typename(a) = error("typename does not apply to this type")
typename(a::DataType) = a.name
function typename(a::Union)
ta = typename(a.a)
tb = typename(a.b)
ta === tb ? tb : error("typename does not apply to unions whose components have different typenames")
end
typename(union::UnionAll) = typename(union.body)

convert{T<:Tuple{Any,Vararg{Any}}}(::Type{T}, x::Tuple{Any, Vararg{Any}}) =
tuple(convert(tuple_type_head(T),x[1]), convert(tuple_type_tail(T), tail(x))...)
convert{T<:Tuple{Any,Vararg{Any}}}(::Type{T}, x::T) = x
Expand Down
157 changes: 138 additions & 19 deletions base/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ type Conditional
end
end

immutable PartialTypeVar
tv::TypeVar
# N.B.: Currently unused, but would allow turning something back
# into Const, if the bounds are pulled out of this TypeVar
lb_certain::Bool
ub_certain::Bool
PartialTypeVar(tv::TypeVar, lb_certain::Bool, ub_certain::Bool) = new(tv, lb_certain, ub_certain)
end

function rewrap(t::ANY, u::ANY)
isa(t, Const) && return t
isa(t, Conditional) && return t
Expand Down Expand Up @@ -688,10 +697,25 @@ function limit_type_depth(t::ANY, d::Int, cov::Bool=true, var::Union{Void,TypeVa
return (cov && !stillcov) ? UnionAll(var, R) : R
end

const DataType_name_fieldindex = fieldindex(DataType, :name)
const DataType_parameters_fieldindex = fieldindex(DataType, :parameters)
const DataType_types_fieldindex = fieldindex(DataType, :types)
const DataType_super_fieldindex = fieldindex(DataType, :super)

const TypeName_name_fieldindex = fieldindex(TypeName, :name)
const TypeName_module_fieldindex = fieldindex(TypeName, :module)
const TypeName_wrapper_fieldindex = fieldindex(TypeName, :wrapper)

function const_datatype_getfield_tfunc(sv, fld)
if (fld == DataType_name_fieldindex ||
Copy link
Contributor

Choose a reason for hiding this comment

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

wrong indent, again

fld == DataType_parameters_fieldindex ||
fld == DataType_types_fieldindex ||
fld == DataType_super_fieldindex)
return abstract_eval_constant(getfield(sv, fld))
end
return nothing
end

# returns (type, isexact)
function getfield_tfunc(s00::ANY, name)
if isa(s00, TypeVar)
Expand Down Expand Up @@ -719,11 +743,22 @@ function getfield_tfunc(s00::ANY, name)
elseif nv === :body || nv === 2
return Const(sv.body)
end
elseif isa(sv, DataType)
t = const_datatype_getfield_tfunc(sv, isa(nv, Symbol) ?
fieldindex(DataType, nv, false) : nv)
t !== nothing && return t
elseif isa(sv, TypeName)
fld = isa(nv, Symbol) ? fieldindex(TypeName, nv, false) : nv
if (fld == TypeName_name_fieldindex ||
fld == TypeName_module_fieldindex ||
fld == TypeName_wrapper_fieldindex)
return abstract_eval_constant(getfield(sv, fld))
end
end
if isa(sv, Module) && isa(nv, Symbol)
return abstract_eval_global(sv, nv)
end
if (isa(sv, DataType) || isimmutable(sv)) && isdefined(sv, nv)
if (isa(sv, SimpleVector) || isimmutable(sv)) && isdefined(sv, nv)
return abstract_eval_constant(getfield(sv, nv))
end
end
Expand Down Expand Up @@ -783,11 +818,9 @@ function getfield_tfunc(s00::ANY, name)
else
sp = nothing
end
if (sp !== nothing &&
(fld == DataType_parameters_fieldindex ||
fld == DataType_types_fieldindex ||
fld == DataType_super_fieldindex))
return Const(getfield(sp, fld))
if sp !== nothing
t = const_datatype_getfield_tfunc(sp, fld)
t !== nothing && return t
end
R = s.types[fld]
if isempty(s.parameters)
Expand Down Expand Up @@ -915,15 +948,20 @@ function apply_type_tfunc(headtypetype::ANY, args::ANY...)
return Any
end
uncertain = false
canconst = true
tparams = Any[]
outervars = Any[]
for i = 1:largs
ai = args[i]
if isType(ai)
aip1 = ai.parameters[1]
canconst &= isleaftype(aip1)
push!(tparams, aip1)
elseif isa(ai, Const) && (isa(ai.val, Type) || valid_tparam(ai.val))
elseif isa(ai, Const) && (isa(ai.val, Type) || isa(ai.val, TypeVar) || valid_tparam(ai.val))
push!(tparams, ai.val)
elseif isa(ai, PartialTypeVar)
canconst = false
push!(tparams, ai.tv)
else
# TODO: return `Bottom` for trying to apply a non-UnionAll
uncertain = true
Expand Down Expand Up @@ -966,11 +1004,11 @@ function apply_type_tfunc(headtypetype::ANY, args::ANY...)
# doesn't match, which could happen if a type estimate is too coarse
return Type{_} where _<:headtype
end
!uncertain && return Const(appl)
!uncertain && canconst && return Const(appl)
if isvarargtype(headtype)
return Type
end
if type_too_complex(appl,0)
if uncertain && type_too_complex(appl,0)
return Type{_} where _<:headtype
end
if istuple
Expand Down Expand Up @@ -1486,6 +1524,25 @@ function Pair_name()
return _Pair_name
end

_typename(a) = Union{}
_typename(a::Vararg) = Any
_typename(a::TypeVar) = Any
_typename(a::DataType) = Const(a.name)
function _typename(a::Union)
ta = _typename(a.a)
tb = _typename(a.b)
ta === tb ? tb : (ta === Any || tb === Any) ? Any : Union{}
end
_typename(union::UnionAll) = _typename(union.body)
function typename_static(t)
# N.B.: typename maps type equivalence classes to a single value
if isa(t, Const) || isType(t)
return _typename(isa(t, Const) ? t.val : t.parameters[1])
else
return Any
end
end

function abstract_call(f::ANY, fargs::Union{Tuple{},Vector{Any}}, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState)
if f === _apply
length(fargs) > 1 || return Any
Expand Down Expand Up @@ -1567,19 +1624,65 @@ function abstract_call(f::ANY, fargs::Union{Tuple{},Vector{Any}}, argtypes::Vect
end
end
return Any
elseif f === UnionAll
if length(fargs) == 3 && isa(argtypes[2], Const)
tv = argtypes[2].val
if isa(tv, TypeVar)
elseif f === TypeVar
lb = Union{}
ub = Any
ub_certain = lb_certain = true
if length(fargs) >= 2 && isa(argtypes[2], Const)
nv = argtypes[2].val
ubidx = 3
if length(fargs) >= 4
ubidx = 4
if isa(argtypes[3], Const)
body = argtypes[3].val
lb = argtypes[3].val
elseif isType(argtypes[3])
body = argtypes[3].parameters[1]
lb = argtypes[3].parameters[1]
lb_certain = false
else
return Any
return TypeVar
end
end
if length(fargs) >= ubidx
if isa(argtypes[ubidx], Const)
ub = argtypes[ubidx].val
elseif isType(argtypes[ubidx])
ub = argtypes[ubidx].parameters[1]
ub_certain = false
else
return TypeVar
end
return abstract_eval_constant(UnionAll(tv, body))
end
tv = TypeVar(nv, lb, ub)
return PartialTypeVar(tv, lb_certain, ub_certain)
end
return TypeVar
elseif f === UnionAll
if length(fargs) == 3
canconst = true
if isa(argtypes[3], Const)
body = argtypes[3].val
elseif isType(argtypes[3])
body = argtypes[3].parameters[1]
canconst = false
else
return Any
end
if isa(argtypes[2], Const)
tv = argtypes[2].val
elseif isa(argtypes[2], PartialTypeVar)
ptv = argtypes[2]
tv = ptv.tv
canconst = false
Copy link
Member

Choose a reason for hiding this comment

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

Does this depend on lb_certain and ub_certain?

Copy link
Member Author

Choose a reason for hiding this comment

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

No. Two invocations of this functions do not create egal types (because it allocates a new one every time). I though we could do something with that at first, but I had to take that out because of that.

else
return Any
end
!isa(tv, TypeVar) && return Any
if !isa(body, Type) && !isa(body, TypeVar)
return Any
end
theunion = UnionAll(tv, body)
ret = canconst ? abstract_eval_constant(theunion) : Type{theunion}
return ret
end
return Any
elseif f === return_type
Expand All @@ -1605,7 +1708,16 @@ function abstract_call(f::ANY, fargs::Union{Tuple{},Vector{Any}}, argtypes::Vect

if length(argtypes)>2 && argtypes[3] ⊑ Int
at2 = widenconst(argtypes[2])
if (at2 <: Tuple ||
if at2 <: SimpleVector && istopfunction(tm, f, :getindex)
if isa(argtypes[2], Const) && isa(argtypes[3], Const)
svecval = argtypes[2].val
idx = argtypes[3].val
if isa(idx, Int) && 1 <= idx <= length(svecval) &
isassigned(svecval, idx)
return Const(getindex(svecval, idx))
end
end
elseif (at2 <: Tuple ||
(isa(at2, DataType) && (at2::DataType).name === Pair_name()))
# allow tuple indexing functions to take advantage of constant
# index arguments.
Expand All @@ -1627,6 +1739,12 @@ function abstract_call(f::ANY, fargs::Union{Tuple{},Vector{Any}}, argtypes::Vect

if istopfunction(tm, f, :promote_type) || istopfunction(tm, f, :typejoin)
return Type
elseif length(argtypes) == 2 && istopfunction(tm, f, :typename)
Copy link
Member

Choose a reason for hiding this comment

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

we should consider trying to handle this by marking the function @pure instead of doing a partial reimplementation here and trying to deal with all of the same edge cases

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not just pure though, it also maps the type equality equivalence class to one value.

t = argtypes[2]
if isa(t, Const) || isType(t)
return typename_static(t)
end
return Any
end

if sv.params.inlining
Expand Down Expand Up @@ -1915,6 +2033,7 @@ function widenconst(c::Const)
return typeof(c.val)
end
end
widenconst(c::PartialTypeVar) = TypeVar
widenconst(t::ANY) = t

issubstate(a::VarState, b::VarState) = (a.typ ⊑ b.typ && a.undef <= b.undef)
Expand Down Expand Up @@ -3562,7 +3681,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference
if method.name == :getindex || method.name == :next || method.name == :indexed_next
if length(atypes) > 2 && atypes[3] ⊑ Int
at2 = widenconst(atypes[2])
if (at2 <: Tuple ||
if (at2 <: Tuple || at2 <: SimpleVector ||
(isa(at2, DataType) && (at2::DataType).name === Pair_name()))
force_infer = true
end
Expand Down
45 changes: 42 additions & 3 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,12 @@ fieldnames(t::UnionAll) = fieldnames(unwrap_unionall(t))
fieldnames{T<:Tuple}(t::Type{T}) = Int[n for n in 1:nfields(t)]

"""
Base.datatype_name(t::DataType) -> Symbol
Base.datatype_name(t) -> Symbol

Get the name of a `DataType` (without its parent module) as a symbol.
Get the name of a (potentially UnionAll-wrapped) `DataType` (without its parent module) as a symbol.
"""
datatype_name(t::DataType) = t.name.name
datatype_name(t::UnionAll) = datatype_name(unwrap_unionall(t))

"""
Base.datatype_module(t::DataType) -> Module
Expand Down Expand Up @@ -232,6 +233,39 @@ a concrete type that can have instances.
"""
isleaftype(t::ANY) = (@_pure_meta; isa(t, DataType) && t.isleaftype)

"""
Base.isabstract(T)

Determine whether `T` was declared as an abstract type (i.e. using the
`abstract` keyword).
"""
function isabstract(t::ANY)
@_pure_meta
t = unwrap_unionall(t)
isa(t,DataType) && t.abstract
end

"""
Base.parameter_upper_bound(t::UnionAll, idx)

Determine the upper bound of a type parameter in the underlying type. E.g.:
```jldoctest
julia> immutable Foo{T<:AbstractFloat, N}
x::Tuple{T, N}
end

julia> Base.parameter_upper_bound(Foo, 1)
AbstractFloat

julia> Base.parameter_upper_bound(Foo, 2)
Any
```
"""
function parameter_upper_bound(t::UnionAll, idx)
@_pure_meta
rewrap_unionall(unwrap_unionall(t).parameters[idx], t)
end

"""
typeintersect(T, S)

Expand Down Expand Up @@ -816,9 +850,14 @@ function method_exists(f::ANY, t::ANY)
typemax(UInt)) != 0
end

function isambiguous(m1::Method, m2::Method)
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
end
ml = _methods_by_ftype(ti, -1, typemax(UInt))
isempty(ml) && return true
for m in ml
Expand Down
4 changes: 2 additions & 2 deletions base/test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,7 @@ defined in the specified modules. Use `imported=true` if you wish to
also test functions that were imported into these modules from
elsewhere.
"""
function detect_ambiguities(mods...; imported::Bool=false)
function detect_ambiguities(mods...; imported::Bool=false, allow_bottom::Bool=true)
function sortdefs(m1, m2)
ord12 = m1.file < m2.file
if !ord12 && (m1.file == m2.file)
Expand All @@ -1145,7 +1145,7 @@ function detect_ambiguities(mods...; imported::Bool=false)
for m in mt
if m.ambig !== nothing
for m2 in m.ambig
if Base.isambiguous(m, m2)
if Base.isambiguous(m, m2, allow_bottom)
push!(ambs, sortdefs(m, m2))
end
end
Expand Down
11 changes: 11 additions & 0 deletions test/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,17 @@ g11015(::Type{Bool}, ::Bool) = 2.0
@test Int <: Base.return_types(f11015, (AT11015,))[1]
@test f11015(AT11015(true)) === 1

# Inference for some type-level computation
fUnionAll{T}(::Type{T}) = Type{S} where S <: T
@inferred fUnionAll(Real) == Type{T} where T <: Real
@inferred fUnionAll(Rational{T} where T <: AbstractFloat) == Type{T} where T<:(Rational{S} where S <: AbstractFloat)

fComplicatedUnionAll{T}(::Type{T}) = Type{Tuple{S,rand() >= 0.5 ? Int : Float64}} where S <: T
let pub = Base.parameter_upper_bound, x = fComplicatedUnionAll(Real)
@test pub(pub(x, 1), 1) == Real
Copy link
Member

Choose a reason for hiding this comment

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

Do these belong in the reflection tests instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

I put them here, because at some point I had inference mess up and constant fold this to the wrong answer.

@test pub(pub(x, 1), 2) == Int || pub(pub(x, 1), 2) == Float64
end

# issue #20267
type T20267{T}
inds::Vector{T}
Expand Down
Loading