Skip to content

Commit 0c7bd1a

Browse files
committed
Limit broadcast mechanism over Nullables
1 parent ab984a5 commit 0c7bd1a

File tree

4 files changed

+27
-110
lines changed

4 files changed

+27
-110
lines changed

base/broadcast.jl

Lines changed: 24 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import Base: broadcast, broadcast!
1010
export bitbroadcast, dotview
1111
export broadcast_getindex, broadcast_setindex!
1212

13+
typealias ScalarType Union{Type{Any}, Type{Nullable}}
14+
1315
## Broadcasting utilities ##
1416
# fallbacks for some special cases
1517
@inline broadcast(f, x::Number...) = f(x...)
@@ -29,37 +31,28 @@ containertype(::Type) = Any
2931
containertype{T<:Ptr}(::Type{T}) = Any
3032
containertype{T<:Tuple}(::Type{T}) = Tuple
3133
containertype{T<:Ref}(::Type{T}) = Array
32-
containertype{T<:AbstractArray}(::Type{T}) =
33-
is_nullable_array(T) ? Array{Nullable} : Array
34+
containertype{T<:AbstractArray}(::Type{T}) = Array
3435
containertype{T<:Nullable}(::Type{T}) = Nullable
3536
containertype(ct1, ct2) = promote_containertype(containertype(ct1), containertype(ct2))
3637
@inline containertype(ct1, ct2, cts...) = promote_containertype(containertype(ct1), containertype(ct2, cts...))
3738

3839
promote_containertype(::Type{Array}, ::Type{Array}) = Array
3940
promote_containertype(::Type{Array}, ct) = Array
4041
promote_containertype(ct, ::Type{Array}) = Array
41-
promote_containertype(::Type{Tuple}, ::Type{Any}) = Tuple
42-
promote_containertype(::Type{Any}, ::Type{Tuple}) = Tuple
42+
promote_containertype(::Type{Tuple}, ::ScalarType) = Tuple
43+
promote_containertype(::ScalarType, ::Type{Tuple}) = Tuple
4344
promote_containertype(::Type{Any}, ::Type{Nullable}) = Nullable
4445
promote_containertype(::Type{Nullable}, ::Type{Any}) = Nullable
45-
promote_containertype(::Type{Nullable}, ::Type{Array}) = Array{Nullable}
46-
promote_containertype(::Type{Array}, ::Type{Nullable}) = Array{Nullable}
47-
promote_containertype(::Type{Array{Nullable}}, ::Type{Array{Nullable}}) =
48-
Array{Nullable}
49-
promote_containertype(::Type{Array{Nullable}}, ::Type{Array}) = Array{Nullable}
50-
promote_containertype(::Type{Array}, ::Type{Array{Nullable}}) = Array{Nullable}
51-
promote_containertype(::Type{Array{Nullable}}, ct) = Array{Nullable}
52-
promote_containertype(ct, ::Type{Array{Nullable}}) = Array{Nullable}
5346
promote_containertype{T}(::Type{T}, ::Type{T}) = T
5447

5548
## Calculate the broadcast indices of the arguments, or error if incompatible
5649
# array inputs
5750
broadcast_indices() = ()
5851
broadcast_indices(A) = broadcast_indices(containertype(A), A)
59-
broadcast_indices(::Union{Type{Any}, Type{Nullable}}, A) = ()
52+
broadcast_indices(::ScalarType, A) = ()
6053
broadcast_indices(::Type{Tuple}, A) = (OneTo(length(A)),)
6154
broadcast_indices(::Type{Array}, A::Ref) = ()
62-
broadcast_indices{T<:Array}(::Type{T}, A) = indices(A)
55+
broadcast_indices(::Type{Array}, A) = indices(A)
6356
@inline broadcast_indices(A, B...) = broadcast_shape((), broadcast_indices(A), map(broadcast_indices, B)...)
6457
# shape (i.e., tuple-of-indices) inputs
6558
broadcast_shape(shape::Tuple) = shape
@@ -133,9 +126,7 @@ end
133126

134127
Base.@propagate_inbounds _broadcast_getindex(A, I) = _broadcast_getindex(containertype(A), A, I)
135128
Base.@propagate_inbounds _broadcast_getindex(::Type{Array}, A::Ref, I) = A[]
136-
Base.@propagate_inbounds _broadcast_getindex(::Type{Any}, A, I) = A
137-
Base.@propagate_inbounds _broadcast_getindex(::Union{Type{Any},
138-
Type{Nullable}}, A, I) = A
129+
Base.@propagate_inbounds _broadcast_getindex(::ScalarType, A, I) = A
139130
Base.@propagate_inbounds _broadcast_getindex(::Any, A, I) = A[I]
140131

141132
## Broadcasting core
@@ -292,22 +283,21 @@ ftype(T::Type, A...) = Type{T}
292283
# if the first argument is Any, then Nullable should be treated like a
293284
# scalar; if the first argument is Array, then Nullable should be treated
294285
# like a container.
295-
typestuple(::Type, a) = (Base.@_pure_meta; Tuple{eltype(a)})
296-
typestuple(::Type{Any}, a::Nullable) = (Base.@_pure_meta; Tuple{typeof(a)})
297-
typestuple(::Type, T::Type) = (Base.@_pure_meta; Tuple{Type{T}})
298-
typestuple{T}(::Type{T}, a, b...) = (Base.@_pure_meta; Tuple{typestuple(T, a).types..., typestuple(T, b...).types...})
286+
typestuple(a) = (Base.@_pure_meta; Tuple{eltype(a)})
287+
typestuple(T::Type) = (Base.@_pure_meta; Tuple{Type{T}})
288+
typestuple(a, b...) = (Base.@_pure_meta; Tuple{typestuple(a).types..., typestuple(b...).types...})
299289

300290
# these functions take the variant of typestuple to be used as first argument
301-
ziptype{T}(::Type{T}, A) = typestuple(T, A)
302-
ziptype{T}(::Type{T}, A, B) = (Base.@_pure_meta; Iterators.Zip2{typestuple(T, A), typestuple(T, B)})
303-
@inline ziptype{T}(::Type{T}, A, B, C, D...) = Iterators.Zip{typestuple(T, A), ziptype(T, B, C, D...)}
291+
ziptype(A) = typestuple(A)
292+
ziptype(A, B) = (Base.@_pure_meta; Iterators.Zip2{typestuple(A), typestuple(B)})
293+
@inline ziptype(A, B, C, D...) = Iterators.Zip{typestuple(A), ziptype(B, C, D...)}
304294

305-
_broadcast_type{S}(::Type{S}, f, T::Type, As...) = Base._return_type(f, typestuple(S, T, As...))
306-
_broadcast_type{T}(::Type{T}, f, A, Bs...) = Base._default_eltype(Base.Generator{ziptype(T, A, Bs...), ftype(f, A, Bs...)})
295+
_broadcast_type(f, T::Type, As...) = Base._return_type(f, typestuple(T, As...))
296+
_broadcast_type(f, A, Bs...) = Base._default_eltype(Base.Generator{ziptype(A, Bs...), ftype(f, A, Bs...)})
307297

308298
# broadcast methods that dispatch on the type of the final container
309299
@inline function broadcast_c(f, ::Type{Array}, A, Bs...)
310-
T = _broadcast_type(Any, f, A, Bs...)
300+
T = _broadcast_type(f, A, Bs...)
311301
shape = broadcast_indices(A, Bs...)
312302
iter = CartesianRange(shape)
313303
if isleaftype(T)
@@ -318,21 +308,14 @@ _broadcast_type{T}(::Type{T}, f, A, Bs...) = Base._default_eltype(Base.Generator
318308
end
319309
return broadcast_t(f, Any, shape, iter, A, Bs...)
320310
end
321-
@inline function broadcast_c(f, ::Type{Array{Nullable}}, A, Bs...)
322-
@inline rec(x) = broadcast(f, x)
323-
@inline rec(x, y) = broadcast(f, x, y)
324-
@inline rec(x, y, z) = broadcast(f, x, y, z)
325-
@inline rec(xs...) = broadcast(f, xs...)
326-
broadcast_c(rec, Array, A, Bs...)
327-
end
328311
function broadcast_c(f, ::Type{Tuple}, As...)
329312
shape = broadcast_indices(As...)
330313
n = length(shape[1])
331314
return ntuple(k->f((_broadcast_getindex(A, k) for A in As)...), n)
332315
end
333316
@inline function broadcast_c(f, ::Type{Nullable}, a...)
334317
nonnull = all(hasvalue, a)
335-
S = _broadcast_type(Array, f, a...)
318+
S = _broadcast_type(f, a...)
336319
if isleaftype(S) && null_safe_eltype_op(f, a...)
337320
Nullable{S}(f(map(unsafe_get, a)...), nonnull)
338321
else
@@ -350,8 +333,8 @@ end
350333
351334
Broadcasts the arrays, tuples, `Ref`, nullables, and/or scalars `As` to a
352335
container of the appropriate type and dimensions. In this context, anything
353-
that is not a subtype of `AbstractArray`, `Ref` (except for `Ptr`s) or `Tuple`,
354-
or `Nullable` is considered a scalar. The resulting container is established by
336+
that is not a subtype of `AbstractArray`, `Ref` (except for `Ptr`s) or `Tuple`
337+
is considered a scalar. The resulting container is established by
355338
the following rules:
356339
357340
- If all the arguments are scalars, it returns a scalar.
@@ -360,16 +343,10 @@ the following rules:
360343
(and treats any `Ref` as a 0-dimensional array of its contents and any tuple
361344
as a 1-dimensional array) expanding singleton dimensions.
362345
363-
The following additional rules apply to `Nullable` arguments:
346+
The following additional rule apply to `Nullable` arguments:
364347
365348
- If there is at least a `Nullable`, and all the arguments are scalars or
366-
`Nullable`, it returns a `Nullable`.
367-
- If there is at least an array or a `Ref` with `Nullable` entries, or there
368-
is at least an array or a `Ref` (perhaps with scalar entries instead of
369-
`Nullable` entries) and a nullable, then the result is an array of
370-
`Nullable` entries.
371-
- If there is a tuple and a nullable, the result is an error, as this case is
372-
not currently supported.
349+
`Nullable`, it returns a `Nullable` treating `Nullable`s as "containers".
373350
374351
A special syntax exists for broadcasting: `f.(args...)` is equivalent to
375352
`broadcast(f, args...)`, and nested `f.(g.(args...))` calls are fused into a
@@ -434,21 +411,8 @@ Nullable{String}("XY")
434411
julia> broadcast(/, 1.0, Nullable(2.0))
435412
Nullable{Float64}(0.5)
436413
437-
julia> [Nullable(1), Nullable(2), Nullable()] .* 3
438-
3-element Array{Nullable{Int64},1}:
439-
3
440-
6
441-
#NULL
442-
443-
julia> [1+im, 2+2im, 3+3im] ./ Nullable{Int}()
444-
3-element Array{Nullable{Complex{Float64}},1}:
445-
#NULL
446-
#NULL
447-
#NULL
448-
449-
julia> Ref(7) .+ Nullable(3)
450-
0-dimensional Array{Nullable{Int64},0}:
451-
10
414+
julia> (1 + im) ./ Nullable{Int}()
415+
Nullable{Complex{Float64}}()
452416
```
453417
"""
454418
@inline broadcast(f, A, Bs...) = broadcast_c(f, containertype(A, Bs...), A, Bs...)

base/sparse/sparsematrix.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,7 +1413,7 @@ function map{Tf,N}(f::Tf, A::SparseMatrixCSC, Bs::Vararg{SparseMatrixCSC,N})
14131413
fofzeros = f(_zeros_eltypes(A, Bs...)...)
14141414
fpreszeros = fofzeros == zero(fofzeros)
14151415
maxnnzC = fpreszeros ? min(length(A), _sumnnzs(A, Bs...)) : length(A)
1416-
entrytypeC = _broadcast_type(Any, f, A, Bs...)
1416+
entrytypeC = _broadcast_type(f, A, Bs...)
14171417
indextypeC = _promote_indtype(A, Bs...)
14181418
Ccolptr = Vector{indextypeC}(A.n + 1)
14191419
Crowval = Vector{indextypeC}(maxnnzC)
@@ -1443,7 +1443,7 @@ function broadcast{Tf,N}(f::Tf, A::SparseMatrixCSC, Bs::Vararg{SparseMatrixCSC,N
14431443
fofzeros = f(_zeros_eltypes(A, Bs...)...)
14441444
fpreszeros = fofzeros == zero(fofzeros)
14451445
indextypeC = _promote_indtype(A, Bs...)
1446-
entrytypeC = _broadcast_type(Any, f, A, Bs...)
1446+
entrytypeC = _broadcast_type(f, A, Bs...)
14471447
Cm, Cn = Base.to_shape(Base.Broadcast.broadcast_indices(A, Bs...))
14481448
maxnnzC = fpreszeros ? _checked_maxnnzbcres(Cm, Cn, A, Bs...) : (Cm * Cn)
14491449
Ccolptr = Vector{indextypeC}(Cn + 1)

test/broadcast.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ StrangeType18623(x,y) = (x,y)
363363
let
364364
f(A, n) = broadcast(x -> +(x, n), A)
365365
@test @inferred(f([1.0], 1)) == [2.0]
366-
g() = (a = 1; Base.Broadcast._broadcast_type(Any, x -> x + a, 1.0))
366+
g() = (a = 1; Base.Broadcast._broadcast_type(x -> x + a, 1.0))
367367
@test @inferred(g()) === Float64
368368
end
369369

test/nullable.jl

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -507,48 +507,6 @@ end
507507
@test Nullable(10.5) ===
508508
@inferred(broadcast(+, 1, 2, Nullable(3), Nullable(4.0), Nullable(1//2)))
509509

510-
# broadcasting for arrays
511-
@test istypeequal(@inferred(broadcast(+, [1, 2, 3], Nullable{Int}(1))),
512-
Nullable{Int}[2, 3, 4])
513-
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[1, 2, 3], 1)),
514-
Nullable{Int}[2, 3, 4])
515-
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[1, 2, 3], Nullable(1))),
516-
Nullable{Int}[2, 3, 4])
517-
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[1, Nullable()], Nullable(1))),
518-
Nullable{Int}[2, Nullable()])
519-
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[Nullable(), 1],
520-
Nullable{Int}())),
521-
Nullable{Int}[Nullable(), Nullable()])
522-
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[Nullable(), 1],
523-
Nullable{Int}[1, Nullable()])),
524-
Nullable{Int}[Nullable(), Nullable()])
525-
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[Nullable(), 1],
526-
Nullable{Int}[Nullable(), 1])),
527-
Nullable{Int}[Nullable(), 2])
528-
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[Nullable(), Nullable()],
529-
Nullable{Int}[1, 2])),
530-
Nullable{Int}[Nullable(), Nullable()])
531-
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[Nullable(), 1],
532-
Nullable{Int}[1])),
533-
Nullable{Int}[Nullable(), 2])
534-
@test istypeequal(@inferred(broadcast(+, Nullable{Float64}[1.0, 2.0],
535-
Nullable{Float64}[1.0 2.0; 3.0 4.0])),
536-
Nullable{Float64}[2.0 3.0; 5.0 6.0])
537-
@test istypeequal(@inferred(broadcast(+, Nullable{Int}[1, 2], [1, 2], 1)),
538-
Nullable{Int}[3, 5])
539-
540-
@test istypeequal(@inferred(broadcast(/, 1, Nullable{Int}[1, 2, 4])),
541-
Nullable{Float64}[1.0, 0.5, 0.25])
542-
@test istypeequal(@inferred(broadcast(muladd, Nullable(2), 42,
543-
[Nullable(1337), Nullable{Int}()])),
544-
Nullable{Int}[1421, Nullable()])
545-
546-
# heterogenous types (not inferrable)
547-
@test istypeequal(broadcast(+, Any[1, 1.0], Nullable(1//2)),
548-
Any[Nullable(3//2), Nullable(1.5)])
549-
@test istypeequal(broadcast(+, Any[Nullable(1) Nullable(1.0)], Nullable(big"1")),
550-
Any[Nullable(big"2") Nullable(big"2.0")])
551-
552510
# test fast path taken
553511
for op in (+, *, -)
554512
for b1 in (false, true)
@@ -557,11 +515,6 @@ for op in (+, *, -)
557515
@inferred(broadcast(op, Nullable{Int}(1, b1),
558516
Nullable{Int}(2, b2)))
559517
end
560-
A = [1, 2, 3]
561-
res = @inferred(broadcast(op, A, Nullable{Int}(1, b1)))
562-
@test res[1] === Nullable{Int}(op(1, 1), b1)
563-
@test res[2] === Nullable{Int}(op(2, 1), b1)
564-
@test res[3] === Nullable{Int}(op(3, 1), b1)
565518
end
566519
end
567520

0 commit comments

Comments
 (0)