diff --git a/NEWS.md b/NEWS.md index b821906be8639..98d4c4cc7de4a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -879,6 +879,10 @@ Deprecated or removed * `map` on dictionaries previously operated on `key=>value` pairs. This behavior is deprecated, and in the future `map` will operate only on values ([#5794]). + * Previously, broadcast defaulted to treating its arguments as scalars if they were not arrays + or had specifically implemented a custom broadcasting implementation. This behavior is + deprecated, and in the future `broadcast` will default to `collect`-ing these arguments ([#26212]). + * Automatically broadcasted `+` and `-` for `array + scalar`, `scalar - array`, and so-on have been deprecated due to inconsistency with linear algebra. Use `.+` and `.-` for these operations instead ([#22880], [#22932]). diff --git a/base/broadcast.jl b/base/broadcast.jl index 7f00b65530327..000c5408889b3 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -48,15 +48,14 @@ BroadcastStyle(::Type{<:Tuple}) = Style{Tuple}() struct Unknown <: BroadcastStyle end BroadcastStyle(::Type{Union{}}) = Unknown() # ambiguity resolution +BroadcastStyle(::Type) = Unknown() """ `Broadcast.Scalar()` is a [`BroadcastStyle`](@ref) indicating that an object is not -treated as a container for the purposes of broadcasting. This is the default for objects -that have not customized `BroadcastStyle`. +treated as a container for the purposes of broadcasting. """ struct Scalar <: BroadcastStyle end -BroadcastStyle(::Type) = Scalar() -BroadcastStyle(::Type{<:Ptr}) = Scalar() +BroadcastStyle(::Type{<:Union{Number,Symbol,String,Type,Function,Uninitialized,RoundingMode,Nothing,Missing}}) = Scalar() """ `Broadcast.AbstractArrayStyle{N} <: BroadcastStyle` is the abstract supertype for any style @@ -366,7 +365,7 @@ end Base.@propagate_inbounds _broadcast_getindex(::Type{T}, I) where T = T Base.@propagate_inbounds _broadcast_getindex(A, I) = _broadcast_getindex(combine_styles(A), A, I) Base.@propagate_inbounds _broadcast_getindex(::DefaultArrayStyle{0}, A::Ref, I) = A[] -Base.@propagate_inbounds _broadcast_getindex(::Union{Unknown,Scalar}, A, I) = A +Base.@propagate_inbounds _broadcast_getindex(::Scalar, A, I) = A Base.@propagate_inbounds _broadcast_getindex(::Any, A, I) = A[I] Base.@propagate_inbounds _broadcast_getindex(::Style{Tuple}, A::Tuple{Any}, I) = A[1] @@ -432,6 +431,12 @@ end end end +collect_unknowns() = () +@inline collect_unknowns(x, rest...) = (collect_unknown(x), collect_unknowns(rest...)...) +@inline collect_unknown(x) = collect_unknown(BroadcastStyle(typeof(x)), x) +collect_unknown(::Any, x) = x +collect_unknown(::Unknown, x) = collect(x) + """ broadcast!(f, dest, As...) @@ -441,7 +446,10 @@ Note that `dest` is only used to store the result, and does not supply arguments to `f` unless it is also listed in the `As`, as in `broadcast!(f, A, A, B)` to perform `A[:] = broadcast(f, A, B)`. """ -@inline broadcast!(f::Tf, dest, As::Vararg{Any,N}) where {Tf,N} = broadcast!(f, dest, combine_styles(As...), As...) +@inline function broadcast!(f::Tf, dest, As::Vararg{Any,N}) where {Tf,N} + As′ = collect_unknowns(As...) + broadcast!(f, dest, combine_styles(As′...), As′...) +end @inline broadcast!(f::Tf, dest, ::BroadcastStyle, As::Vararg{Any,N}) where {Tf,N} = broadcast!(f, dest, nothing, As...) # Default behavior (separated out so that it can be called by users who want to extend broadcast!). @@ -529,7 +537,7 @@ maptoTuple(f, a, b...) = Tuple{f(a), maptoTuple(f, b...).types...} # )::_broadcast_getindex_eltype(A) _broadcast_getindex_eltype(A) = _broadcast_getindex_eltype(combine_styles(A), A) _broadcast_getindex_eltype(::Scalar, ::Type{T}) where T = Type{T} -_broadcast_getindex_eltype(::Union{Unknown,Scalar}, A) = typeof(A) +_broadcast_getindex_eltype(::Scalar, A) = typeof(A) _broadcast_getindex_eltype(::BroadcastStyle, A) = eltype(A) # Tuple, Array, etc. # Inferred eltype of result of broadcast(f, xs...) @@ -611,8 +619,11 @@ julia> string.(("one","two","three","four"), ": ", 1:4) ``` """ -@inline broadcast(f, A, Bs...) = - broadcast(f, combine_styles(A, Bs...), nothing, nothing, A, Bs...) +@inline function broadcast(f, A, Bs...) + A′ = collect_unknown(A) + Bs′ = collect_unknowns(Bs...) + broadcast(f, combine_styles(A′, Bs′...), nothing, nothing, A′, Bs′...) +end @inline broadcast(f, s::BroadcastStyle, ::Nothing, ::Nothing, A, Bs...) = broadcast(f, s, combine_eltypes(f, A, Bs...), combine_indices(A, Bs...), @@ -655,7 +666,7 @@ function broadcast_nonleaf(f, s::NonleafHandlingTypes, ::Type{ElType}, shape::In _broadcast!(f, dest, keeps, Idefaults, As, Val(nargs), iter, st, 1) end -broadcast(f, ::Union{Scalar,Unknown}, ::Nothing, ::Nothing, a...) = f(a...) +broadcast(f, ::Scalar, ::Nothing, ::Nothing, a...) = f(a...) @inline broadcast(f, ::Style{Tuple}, ::Nothing, ::Nothing, A, Bs...) = tuplebroadcast(f, longest_tuple(A, Bs...), A, Bs...) diff --git a/base/deprecated.jl b/base/deprecated.jl index 576369a175812..aa70ebc2599ca 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -1372,6 +1372,16 @@ end # PR #23332 @deprecate ^(x, p::Integer) Base.power_by_squaring(x,p) +# Deprecate defaulting to broadcast unknown values as scalars +function Broadcast.collect_unknown(::Broadcast.Unknown, x) + depwarn("treating x::$(typeof(x)) as a scalar in broadcast is deprecated, use `Ref(x)` instead", (:broadcast, :broadcast!)) + return Ref(x) +end +function Broadcast.collect_unknown(::Broadcast.Unknown, x::Union{AbstractArray,Ref}) + depwarn("treating x::$(typeof(x)) as a scalar in broadcast is deprecated, use `fill(x)` instead", (:broadcast, :broadcast!)) + return fill(x) +end + # Issue #25979 # The `remove_destination` keyword to `cp`, `mv`, and the unexported `cptree` has been # renamed to `force`. To remove this deprecation, remove the `remove_destination` keyword diff --git a/stdlib/Dates/src/arithmetic.jl b/stdlib/Dates/src/arithmetic.jl index f1ca82e71c087..5a7df174acfcb 100644 --- a/stdlib/Dates/src/arithmetic.jl +++ b/stdlib/Dates/src/arithmetic.jl @@ -97,3 +97,6 @@ end # AbstractArray{TimeType}, AbstractArray{TimeType} (-)(x::OrdinalRange{T}, y::OrdinalRange{T}) where {T<:TimeType} = Vector(x) - Vector(y) (-)(x::AbstractRange{T}, y::AbstractRange{T}) where {T<:TimeType} = Vector(x) - Vector(y) + +# Allow dates and times to broadcast as unwrapped scalars +Base.Broadcast.BroadcastStyle(::Type{<:AbstractTime}) = Base.Broadcast.Scalar() diff --git a/stdlib/Dates/src/io.jl b/stdlib/Dates/src/io.jl index 0efef534b7510..9cd04abd186e7 100644 --- a/stdlib/Dates/src/io.jl +++ b/stdlib/Dates/src/io.jl @@ -377,6 +377,8 @@ function Base.show(io::IO, df::DateFormat) write(io, '"') end +Base.Broadcast.BroadcastStyle(::Type{<:DateFormat}) = Base.Broadcast.Scalar() + """ dateformat"Y-m-d H:M:S" diff --git a/test/broadcast.jl b/test/broadcast.jl index f094027160e27..2dadc510e2397 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -407,7 +407,7 @@ let end # Ref as 0-dimensional array for broadcast -@test (-).(C_NULL, C_NULL)::UInt == 0 +@test (-).(fill(C_NULL), fill(C_NULL))::UInt == fill(0) @test (+).(1, Ref(2)) == fill(3) @test (+).(Ref(1), Ref(2)) == fill(3) @test (+).([[0,2], [1,3]], Ref{Vector{Int}}([1,-1])) == [[1,1], [2,2]]