Skip to content
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

Unpirate Union{}[] #685

Merged
merged 11 commits into from
Nov 7, 2019
Merged

Unpirate Union{}[] #685

merged 11 commits into from
Nov 7, 2019

Conversation

tkf
Copy link
Member

@tkf tkf commented Nov 6, 2019

Union{}[] does not work after I import StaticArrays (cb46c2d) due to the new SA[...] constructor:

julia> Union{}[]
0-element Array{Union{},1}

julia> using StaticArrays

julia> Union{}[]
ERROR: ArgumentError: Union{} does not have elements
Stacktrace:
 [1] eltype(::Type{Union{}}) at ./array.jl:124
 [2] similar_type(::Type{Union{}}, ::Size{(0,)}) at /home/takafumi/.julia/dev/StaticArrays/src/abstractarray.jl:66
 [3] getindex(::Core.TypeofBottom) at /home/takafumi/.julia/dev/StaticArrays/src/initializers.jl:29
 [4] top-level scope at REPL[3]:1

This PR fixes it by overloading type piracy by yet another type piracy.

(Though I'm not sure if this is technically a type piracy. If you can't overload anything that can accept Type{Union{}}, you can't write Type{<:MyType}. But then getindex(::Type{Union{}}) = ... just totally looks like a type piracy...)

Copy link
Member

@andyferris andyferris left a comment

Choose a reason for hiding this comment

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

I guess this is the correct way to do this?

Although would Vector{Union{}}() be clearer and avoid a run-time invoke?

@tkf
Copy link
Member Author

tkf commented Nov 6, 2019

It seems invoke(getindex, Tuple{Type}, Union{}) and Vector{Union{}}() are equivalent in terms of codegen:

julia> @inline f() = invoke(getindex, Tuple{Type}, Union{})
f (generic function with 1 method)

julia> @inline g() = Vector{Union{}}()
g (generic function with 1 method)

julia> @code_typed f()
CodeInfo(
1%1 = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{Union{},1}, svec(Any, Int64), 0, :(:ccall), Array{Union{},1}, 0, 0))::Array{Union{},1}
└──      return %1
) => Array{Union{},1}

julia> @code_typed g()
CodeInfo(
1%1 = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{Union{},1}, svec(Any, Int64), 0, :(:ccall), Array{Union{},1}, 0, 0))::Array{Union{},1}
└──      return %1
) => Array{Union{},1}

I personally prefer invoke(getindex, Tuple{Type}, Union{}) as it avoids us to refer to the implementation detail (Base can in principle do something different).

@tkf
Copy link
Member Author

tkf commented Nov 6, 2019

I should mention that importing StaticArrays still alters the type of error thrown by Union{}[1].

Before importing StaticArrays:

julia> Union{}[1]
ERROR: MethodError: convert(::Type{Union{}}, ::Int64) is ambiguous. Candidates:
  convert(::Type{Union{}}, x) in Base at essentials.jl:168
  convert(::Type{T}, x::Number) where T<:Number in Base at number.jl:7
  convert(::Type{T}, arg) where T<:VecElement in Base at baseext.jl:8
  convert(::Type{T}, x::Number) where T<:AbstractChar in Base at char.jl:179
Possible fix, define
  convert(::Type{Union{}}, ::Number)
Stacktrace:
 [1] setindex!(::Array{Union{},1}, ::Int64, ::Int64) at ./array.jl:796
 [2] getindex(::Type{Union{}}, ::Int64) at ./array.jl:366
 [3] top-level scope at REPL[1]:1

After importing StaticArrays (of this PR):

julia> Union{}[1]
ERROR: ArgumentError: Union{} does not have elements
Stacktrace:
 [1] eltype(::Type{Union{}}) at ./array.jl:124
 [2] similar_type(::Type{Union{}}, ::Size{(1,)}) at /home/takafumi/.julia/dev/StaticArrays/src/abstractarray.jl:66
 [3] getindex(::Core.TypeofBottom, ::Int64) at /home/takafumi/.julia/dev/StaticArrays/src/initializers.jl:30
 [4] top-level scope at REPL[14]:1

Typically exception type is not in the API domain so I'm not sure if we need to care, though.

@c42f
Copy link
Member

c42f commented Nov 6, 2019

Oh wow this is nasty :-/ Good catch.

I'm not sure this is the right way to go about fixing this. Adding a lower bound seems like it might work instead?

@inline Base.getindex(sa::Type{T}, xs...) where {E, SA{E}<:T<:SA} = similar_type(sa, Size(length(xs)))(xs)

We will need fixes for typed_vcat and typed_hcat too I guess to avoid these cases going into the static arrays code and changing the error...

@tkf
Copy link
Member Author

tkf commented Nov 6, 2019

Thanks, that's much more elegant.

@coveralls
Copy link

coveralls commented Nov 6, 2019

Coverage Status

Coverage increased (+0.03%) to 81.718% when pulling 39fc862 on tkf:union into cb46c2d on JuliaArrays:master.

@c42f c42f added the bugfix label Nov 6, 2019
src/initializers.jl Outdated Show resolved Hide resolved
@c42f
Copy link
Member

c42f commented Nov 6, 2019

Looks good, cheers! I think we can merge this, though I added a suggested comment because having a lower bound is extremely unusual.

The fact that this was a problem at all is kind of concerning. I've written a lot of foo(::Type{<:X}) before.

Co-Authored-By: Chris Foster <chris42f@gmail.com>
@@ -26,6 +26,7 @@ const SA_F64 = SA{Float64}
@inline similar_type(::Type{SA}, ::Size{S}) where {S} = SArray{Tuple{S...}}
@inline similar_type(::Type{SA{T}}, ::Size{S}) where {T,S} = SArray{Tuple{S...}, T}

# Lower bound `SA{E}<:T` prevents `T == Union{}` from accidentally matching this signature.
@inline Base.getindex(sa::Type{T}, xs...) where {E, SA{E}<:T<:SA} = similar_type(sa, Size(length(xs)))(xs)
Copy link
Member

Choose a reason for hiding this comment

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

What about SA precisely?

Copy link
Member

Choose a reason for hiding this comment

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

There's nothing special about SA... It's just that ::Type{<:SA} is considered more specific to matching Union{} than a generic ::Type{T}:

julia> foo(::Type{T}) where {T} = "generic"
julia> foo(::Type{<:Int}) = "special"
julia> foo(Union{})
"special"

Copy link
Member

Choose a reason for hiding this comment

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

somewhat disturbing eh?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I get that part.

I can see the above is fine for SA{Int64}[1,2,3]. But what about vanilla SA[1,2,3]? We don't have SA{E} <: SA for any (invariant) E, do we?

Copy link
Member

@c42f c42f Nov 6, 2019

Choose a reason for hiding this comment

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

Oh right, I didn't understand your question. You're asking why T === SA works here?

We do have SA{E} <: SA for any choice of E, but then E doesn't have a definite value which is kind of weird. Actually this kind of thing has caused the inliner to fail in the past (see #665) for some reason.

The normalization of the following is also doing my head in a bit

julia> T where SA{E} <: T <: SA where E
SA

@c42f
Copy link
Member

c42f commented Nov 6, 2019

Uh oh. It turns out that the compiler (inliner?) doesn't like this implementation:

julia> foo() = SA[1,2,3]
foo (generic function with 1 method)

julia> @code_typed optimize=true foo()
CodeInfo(
1%1 = Base.getindex(Main.SA, 1, 2, 3)::Core.Compiler.Const([1, 2, 3], false)
└──      return %1
) => SArray{Tuple{3},Int64,1,3}

Strangely enough, inference seems to have worked really well and constant propagated everything, but the getindex call is still there. What.

@tkf
Copy link
Member Author

tkf commented Nov 6, 2019

I rollback to invoke-based solution.

@c42f
Copy link
Member

c42f commented Nov 7, 2019

I still feel bad about having a special case defined for Union{}, so I tweaked this another way by duplicating the function signatures. Does that look ok to you?

The lower bound optimization issue seems related to JuliaLang/julia#30713

@tkf
Copy link
Member Author

tkf commented Nov 7, 2019

Thanks! That's much better.

@c42f c42f merged commit 7819e3a into JuliaArrays:master Nov 7, 2019
@tkf tkf deleted the union branch November 7, 2019 03:09
@tkf
Copy link
Member Author

tkf commented Nov 7, 2019

I just noticed another approach was

Type₁(U) = Type{T} where {P, U{P} <: T <: U}
@inline Base.getindex(sa::Type₁(SA), xs...) = similar_type(sa, Size(length(xs)))(xs)

Inliner does seem to work with this.

Maybe it's nice to have something like Type₊(U::UnionAll) that generalize Type₁ to arbitrary number of parameters. I think it makes sense to add it to Base as (AFAIU) it's not possible to implement this without referring to the internal representation of UnionAll.

@tkf
Copy link
Member Author

tkf commented Nov 7, 2019

I opened an issue here: JuliaLang/julia#33780

@c42f
Copy link
Member

c42f commented Nov 7, 2019

Thanks, excellent.

By the way do you need this fix urgently? We could release a 0.12.1 more or less immediately if that helps.

@tkf
Copy link
Member Author

tkf commented Nov 7, 2019

Thanks, but it's not urgent. I just excluded 0.12 in the projects that hits this bug.

But now that the registry is automated maybe you can go with more frequency release cycle?

@c42f
Copy link
Member

c42f commented Nov 7, 2019

Yes, I think I'll release soon anyway. I thought @oxinabox's blog post on continuous delivery had some good points (most especially for bug fixes) https://white.ucc.asn.au/2019/09/28/Continuous-Delivery-For-Julia-Packages.html though it would be nice to be a tad more automated.

I like your Type₁ solution from JuliaLang/julia#33780. I wanted to do something like that but forgot to include the Type on the inside of the UnionAll and got confused.

@tkf
Copy link
Member Author

tkf commented Nov 7, 2019

I think that's a great ideal. But I can see that you maybe need to be a bit more conservative for "almost-Base" package like StaticArrays.jl. I guess it'd be nice to do some reverse testing to make CD safer. JuliaCIBot was one of the rare things it was better before 1.0 in Julia....

@c42f
Copy link
Member

c42f commented Nov 8, 2019

Yeah, well 0.12.1 is very conservatively just patching this particular issue so I just released it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants