Skip to content

Commit

Permalink
Merge 05cc1e2 into dd49081
Browse files Browse the repository at this point in the history
  • Loading branch information
jrevels authored Jun 22, 2018
2 parents dd49081 + 05cc1e2 commit 6b81c5b
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 112 deletions.
15 changes: 10 additions & 5 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,15 @@ add_tfunc(throw, 1, 1, (@nospecialize(x)) -> Bottom, 0)
# returns (type, isexact)
# if isexact is false, the actual runtime type may (will) be a subtype of t
function instanceof_tfunc(@nospecialize(t))
if t === Bottom || t === typeof(Bottom)
return Bottom, true
elseif isa(t, Const)
if isa(t, Const)
if isa(t.val, Type)
return t.val, true
end
return Bottom, true
end
t = widenconst(t)
if t === Bottom || t === typeof(Bottom) || typeintersect(t, Type) === Bottom
return Bottom, true
elseif isType(t)
tp = t.parameters[1]
return tp, !has_free_typevars(tp)
Expand Down Expand Up @@ -391,15 +394,17 @@ add_tfunc(isa, 2, 2,
if t === Bottom
return Const(false)
elseif v t
if isexact
if isexact && isnotbrokensubtype(v, t)
return Const(true)
end
elseif isa(v, Const) || isa(v, Conditional) || isdispatchelem(v)
# this tests for knowledge of a leaftype appearing on the LHS
# (ensuring the isa is precise)
return Const(false)
elseif isexact && typeintersect(v, t) === Bottom
if !iskindtype(v) #= subtyping currently intentionally answers this query incorrectly for kinds =#
# similar to `isnotbrokensubtype` check above, `typeintersect(v, t)`
# can't be trusted for kind types so we do an extra check here
if !iskindtype(v)
return Const(false)
end
end
Expand Down
8 changes: 7 additions & 1 deletion base/compiler/typeutils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ function issingletontype(@nospecialize t)
return false
end

# Subtyping currently intentionally answers certain queries incorrectly for kind types. For
# some of these queries, this check can be used to somewhat protect against making incorrect
# decisions based on incorrect subtyping. Note that this check, itself, is broken for
# certain combinations of `a` and `b` where one/both isa/are `Union`/`UnionAll` type(s)s.
isnotbrokensubtype(a, b) = (!iskindtype(b) || !isType(a) || issingletontype(a.parameters[1]))

argtypes_to_type(argtypes::Array{Any,1}) = Tuple{anymap(widenconst, argtypes)...}

function isknownlength(t::DataType)
Expand All @@ -52,7 +58,7 @@ end
# return an upper-bound on type `a` with type `b` removed
# such that `return <: a` && `Union{return, b} == Union{a, b}`
function typesubtract(@nospecialize(a), @nospecialize(b))
if a <: b
if a <: b && isnotbrokensubtype(a, b)
return Bottom
end
if isa(a, Union)
Expand Down
40 changes: 9 additions & 31 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -141,33 +141,6 @@ end
argtail(x, rest...) = rest
tail(x::Tuple) = argtail(x...)

# TODO: a better / more infer-able definition would pehaps be
# tuple_type_head(T::Type) = fieldtype(T::Type{<:Tuple}, 1)
tuple_type_head(T::UnionAll) = (@_pure_meta; UnionAll(T.var, tuple_type_head(T.body)))
function tuple_type_head(T::Union)
@_pure_meta
return Union{tuple_type_head(T.a), tuple_type_head(T.b)}
end
function tuple_type_head(T::DataType)
@_pure_meta
T.name === Tuple.name || throw(MethodError(tuple_type_head, (T,)))
return unwrapva(T.parameters[1])
end

tuple_type_tail(T::UnionAll) = (@_pure_meta; UnionAll(T.var, tuple_type_tail(T.body)))
function tuple_type_tail(T::Union)
@_pure_meta
return Union{tuple_type_tail(T.a), tuple_type_tail(T.b)}
end
function tuple_type_tail(T::DataType)
@_pure_meta
T.name === Tuple.name || throw(MethodError(tuple_type_tail, (T,)))
if isvatuple(T) && length(T.parameters) == 1
return T
end
return Tuple{argtail(T.parameters...)...}
end

tuple_type_cons(::Type, ::Type{Union{}}) = Union{}
function tuple_type_cons(::Type{S}, ::Type{T}) where T<:Tuple where S
@_pure_meta
Expand Down Expand Up @@ -243,10 +216,15 @@ function typename(a::Union)
end
typename(union::UnionAll) = typename(union.body)

convert(::Type{T}, x::T) where {T<:Tuple{Any, Vararg{Any}}} = x
convert(::Type{Tuple{}}, x::Tuple{Any, Vararg{Any}}) = throw(MethodError(convert, (Tuple{}, x)))
convert(::Type{T}, x::Tuple{Any, Vararg{Any}}) where {T<:Tuple} =
(convert(tuple_type_head(T), x[1]), convert(tuple_type_tail(T), tail(x))...)
convert(::Type{T}, x::T) where {T<:Tuple} = x

tuple_convert_check(T, x) = isvatuple(T) || nfields(x) === fieldcount(T) || throw(MethodError(convert, (T, x)))

function convert(::Type{T}, x::NTuple{N,Any}) where {T<:Tuple,N}
tuple_convert_check(T, x)
# TODO: this is inferring Unions for concrete converts
return ntuple(i -> convert(fieldtype(T, i), x[i]), Val(N))
end

# TODO: the following definitions are equivalent (behaviorally) to the above method
# I think they may be faster / more efficient for inference,
Expand Down
33 changes: 21 additions & 12 deletions base/iterators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import ..@__MODULE__, ..parentmodule
const Base = parentmodule(@__MODULE__)
using .Base:
@inline, Pair, AbstractDict, IndexLinear, IndexCartesian, IndexStyle, AbstractVector, Vector,
tail, tuple_type_head, tuple_type_tail, tuple_type_cons, SizeUnknown, HasLength, HasShape,
IsInfinite, EltypeUnknown, HasEltype, OneTo, @propagate_inbounds, Generator, AbstractRange,
LinearIndices, (:), |, +, -, !==, !, <=, <, missing
tail, tuple_type_cons, SizeUnknown, HasLength, HasShape, IsInfinite, EltypeUnknown, HasEltype,
OneTo, @propagate_inbounds, Generator, AbstractRange, LinearIndices, (:), |, +, -, !==, !, <=,
<, missing

import .Base:
first, last,
Expand Down Expand Up @@ -749,9 +749,12 @@ julia> collect(Iterators.product(1:2, 3:5))
"""
product(iters...) = ProductIterator(iters)

IteratorSize(::Type{ProductIterator{Tuple{}}}) = HasShape{0}()
IteratorSize(::Type{ProductIterator{T}}) where {T<:Tuple} =
prod_iteratorsize( IteratorSize(tuple_type_head(T)), IteratorSize(ProductIterator{tuple_type_tail(T)}) )
function IteratorSize(::Type{ProductIterator{T}}) where {T<:Tuple}
if isvatuple(T)
throw(ArgumentError("Cannot compute IteratorSize for ProductIterator{$T}"))
end
return reduce(prod_iteratorsize, HasShape{0}(), ntuple(i -> IteratorSize(fieldtype(T, i))), fieldcount(T))
end

prod_iteratorsize(::HasLength, ::HasLength) = HasShape{2}()
prod_iteratorsize(::HasLength, ::HasShape{N}) where {N} = HasShape{N+1}()
Expand Down Expand Up @@ -787,15 +790,21 @@ _length(p::ProductIterator) = prod(map(unsafe_length, axes(p)))
IteratorEltype(::Type{ProductIterator{Tuple{}}}) = HasEltype()
IteratorEltype(::Type{ProductIterator{Tuple{I}}}) where {I} = IteratorEltype(I)
function IteratorEltype(::Type{ProductIterator{T}}) where {T<:Tuple}
I = tuple_type_head(T)
P = ProductIterator{tuple_type_tail(T)}
IteratorEltype(I) == EltypeUnknown() ? EltypeUnknown() : IteratorEltype(P)
if isvatuple(T)
throw(ArgumentError("Cannot compute IteratorEltype for ProductIterator{$T}"))
elseif any(ntuple(i -> IteratorEltype(fieldtype(T, i)) == EltypeUnknown(), fieldcount(T)))
return EltypeUnknown()
end
return HasEltype()
end

eltype(::Type{<:ProductIterator{I}}) where {I} = _prod_eltype(I)
_prod_eltype(::Type{Tuple{}}) = Tuple{}
_prod_eltype(::Type{I}) where {I<:Tuple} =
Base.tuple_type_cons(eltype(tuple_type_head(I)),_prod_eltype(tuple_type_tail(I)))
function _prod_eltype(::Type{I}) where {I<:Tuple}
if isvatuple(I)
throw(ArgumentError("Cannot compute _prod_eltype for tuple type $I"))
end
return Tuple{ntuple(i -> eltype(fieldtype(I, i)), fieldcount(I))...}
end

iterate(::ProductIterator{Tuple{}}) = (), true
iterate(::ProductIterator{Tuple{}}, state) = nothing
Expand Down
2 changes: 0 additions & 2 deletions base/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,6 @@ precompile(Tuple{typeof(Base.take!), Base.Channel{Any}})
precompile(Tuple{typeof(Base.task_done_hook), Task})
precompile(Tuple{typeof(Base.time_print), UInt64, Int64, Int64, Int64})
precompile(Tuple{typeof(Base.truncate), Base.GenericIOBuffer{Array{UInt8, 1}}, Int64})
precompile(Tuple{typeof(Base.tuple_type_head), Type{Tuple{Vararg{Int64, N}} where N}})
precompile(Tuple{typeof(Base.tuple_type_tail), Type{Tuple{Vararg{Int64, N}} where N}})
precompile(Tuple{typeof(Base.typed_vcat), Type{Any}, Array{Symbol, 1}, Array{Symbol, 1}, Array{Symbol, 1}, Array{Symbol, 1}, Array{Symbol, 1}, Array{String, 1}})
precompile(Tuple{typeof(Base.typeinfo_eltype), Type{Any}})
precompile(Tuple{typeof(Base.unique), Array{Any, 1}})
Expand Down
2 changes: 2 additions & 0 deletions base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ include("iterators.jl")
using .Iterators: zip, enumerate
using .Iterators: Flatten, product # for generators

include("tupleconstructor.jl") # constructing a Tuple from an iterator

include("namedtuple.jl")

# numeric operations
Expand Down
42 changes: 0 additions & 42 deletions base/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -219,48 +219,6 @@ fill_to_length(t::Tuple{}, val, ::Val{2}) = (val, val)
# return (t..., ntuple(i -> val, N - length(t))...)
#end

# constructing from an iterator

# only define these in Base, to avoid overwriting the constructors
# NOTE: this means this constructor must be avoided in Core.Compiler!
if nameof(@__MODULE__) === :Base

(::Type{T})(x::Tuple) where {T<:Tuple} = convert(T, x) # still use `convert` for tuples

# resolve ambiguity between preceding and following methods
All16{E,N}(x::Tuple) where {E,N} = convert(All16{E,N}, x)

function (T::All16{E,N})(itr) where {E,N}
len = N+16
elts = collect(E, Iterators.take(itr,len))
if length(elts) != len
_totuple_err(T)
end
(elts...,)
end

(::Type{T})(itr) where {T<:Tuple} = _totuple(T, itr)

_totuple(::Type{Tuple{}}, itr, s...) = ()

function _totuple_err(@nospecialize T)
@_noinline_meta
throw(ArgumentError("too few elements for tuple type $T"))
end

function _totuple(T, itr, s...)
@_inline_meta
y = iterate(itr, s...)
y === nothing && _totuple_err(T)
(convert(tuple_type_head(T), y[1]), _totuple(tuple_type_tail(T), itr, y[2])...)
end

_totuple(::Type{Tuple{Vararg{E}}}, itr, s...) where {E} = (collect(E, Iterators.rest(itr,s...))...,)

_totuple(::Type{Tuple}, itr, s...) = (collect(Iterators.rest(itr,s...))...,)

end

## comparison ##

isequal(t1::Tuple, t2::Tuple) = (length(t1) == length(t2)) && _isequal(t1, t2)
Expand Down
60 changes: 60 additions & 0 deletions base/tupleconstructor.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

# NOTE: This is in a separate file from tuple.jl so that it can be conditionally included
# in sysimg.jl after the necessary machinery for `@generated` is defined. Furthermore,
# this code must not be loaded into Core.Compiler.

if nameof(@__MODULE__) === :Base

(::Type{T})(x::Tuple) where {T<:Tuple} = convert(T, x) # still use `convert` for tuples

# resolve ambiguity between preceding and following methods
All16{E,N}(x::Tuple) where {E,N} = convert(All16{E,N}, x)

function (T::All16{E,N})(itr) where {E,N}
len = N+16
elts = collect(E, Iterators.take(itr,len))
if length(elts) != len
_totuple_err(T)
end
(elts...,)
end

@generated function (::Type{T})(itr)::T where {T<:Tuple}
tuple_expr = Expr(:tuple)
if isvatuple(T)
t = unwrap_unionall(T)
n = length(t.parameters) - 1
else
n = fieldcount(T)
end
for i in 1:n
push!(tuple_expr.args, quote
if done(itr, state)
_totuple_err(T)
else
item, state = next(itr, state)
convert(fieldtype(T, $i), item)
end
end)
end
if isvatuple(T)
V = rewrap_unionall(unwrap_unionall(t.parameters[n + 1]), T)
U = unwrapva(V)
if n == 0 # then avoid creating a redundant iterator
return :((collect($U, itr)...,))
end
push!(tuple_expr.args, Expr(:..., :(collect($U, Iterators.rest(itr, state)))))
end
return quote
state = start(itr)
$tuple_expr
end
end

function _totuple_err(@nospecialize T)
@_noinline_meta
throw(ArgumentError("too few elements for tuple type $T"))
end

end # nameof(@__MODULE__) === :Base
45 changes: 26 additions & 19 deletions src/cgutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1046,26 +1046,33 @@ static void emit_type_error(jl_codectx_t &ctx, const jl_cgval_t &x, Value *type,

static std::pair<Value*, bool> emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x, jl_value_t *type, const std::string *msg)
{
Optional<bool> known_isa;
jl_value_t *intersected_type = type;
if (x.constant)
known_isa = jl_isa(x.constant, type);
else if (jl_subtype(x.typ, type))
known_isa = true;
else {
intersected_type = jl_type_intersection(x.typ, type);
if (intersected_type == (jl_value_t*)jl_bottom_type)
known_isa = false;
}
if (known_isa) {
if (!*known_isa && msg) {
emit_type_error(ctx, x, literal_pointer_val(ctx, type), *msg);
ctx.builder.CreateUnreachable();
BasicBlock *failBB = BasicBlock::Create(jl_LLVMContext, "fail", ctx.f);
ctx.builder.SetInsertPoint(failBB);
}
return std::make_pair(ConstantInt::get(T_int1, *known_isa), true);
}

// TODO: This optimization suffers from incorrectness issues due to broken subtyping for
// kind types (see https://github.com/JuliaLang/julia/issues/27078). For actual `isa`
// calls, this optimization should already have been performed upstream anyway, but
// having this optimization in codegen might still be beneficial for `typeassert`s
// if we can make it correct.
//
// Optional<bool> known_isa;
// if (x.constant)
// known_isa = jl_isa(x.constant, type);
// else if (jl_subtype(x.typ, type))
// known_isa = true;
// else {
// intersected_type = jl_type_intersection(x.typ, type);
// if (intersected_type == (jl_value_t*)jl_bottom_type)
// known_isa = false;
// }
// if (known_isa) {
// if (!*known_isa && msg) {
// emit_type_error(ctx, x, literal_pointer_val(ctx, type), *msg);
// ctx.builder.CreateUnreachable();
// BasicBlock *failBB = BasicBlock::Create(jl_LLVMContext, "fail", ctx.f);
// ctx.builder.SetInsertPoint(failBB);
// }
// return std::make_pair(ConstantInt::get(T_int1, *known_isa), true);
// }

// intersection with Type needs to be handled specially
if (jl_has_intersect_type_not_kind(type)) {
Expand Down
5 changes: 5 additions & 0 deletions test/compiler/compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1683,3 +1683,8 @@ struct Foo19668
Foo19668(; kwargs...) = new()
end
@test Base.return_types(Foo19668, ()) == [Foo19668]

# issue #27078
f27078(T::Type{S}) where {S} = isa(T, UnionAll) ? f27078(T.body) : T
T27078 = Vector{Vector{T}} where T
@test f27078(T27078) === T27078.body
2 changes: 2 additions & 0 deletions test/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ end
@test Tuple{Vararg{Float32}}(Float64[1,2,3]) === (1.0f0, 2.0f0, 3.0f0)
@test Tuple{Int,Vararg{Float32}}(Float64[1,2,3]) === (1, 2.0f0, 3.0f0)
@test Tuple{Int,Vararg{Any}}(Float64[1,2,3]) === (1, 2.0, 3.0)
@test (Tuple{Vararg{T}} where T<:AbstractFloat)([1,2,3]) === (1.0, 2.0, 3.0)
@test (Tuple{Tuple{T,T},Vararg{T}} where T<:AbstractFloat)([(1,2.0),3,4]) === ((1.0, 2.0), 3.0, 4.0)
@test Tuple(fill(1.,5)) === (1.0,1.0,1.0,1.0,1.0)
@test_throws MethodError convert(Tuple, fill(1.,5))

Expand Down

0 comments on commit 6b81c5b

Please sign in to comment.