diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 453b55caffa42..d99243a56e969 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1196,13 +1196,14 @@ function semi_concrete_eval_call(interp::AbstractInterpreter, # state = InliningState(interp) # ir = ssa_inlining_pass!(irsv.ir, state, propagate_inbounds(irsv)) effects = result.effects - if !is_nothrow(effects) - effects = Effects(effects; nothrow) + if nothrow + effects = Effects(effects; nothrow=true) end if noub - effects = Effects(effects; noub = ALWAYS_TRUE) + effects = Effects(effects; noub=ALWAYS_TRUE) end - return ConstCallResults(rt, result.exct, SemiConcreteResult(mi, ir, effects), effects, mi) + exct = refine_exception_type(result.exct, effects) + return ConstCallResults(rt, exct, SemiConcreteResult(mi, ir, effects), effects, mi) end end end @@ -2136,7 +2137,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), argtypes = Any[typeof(<:), argtypes[3], argtypes[2]] return abstract_call_known(interp, <:, ArgInfo(fargs, argtypes), si, sv, max_methods) elseif la == 2 && istopfunction(f, :typename) - return CallMeta(typename_static(argtypes[2]), Any, EFFECTS_TOTAL, MethodResultPure()) + return CallMeta(typename_static(argtypes[2]), Bottom, EFFECTS_TOTAL, MethodResultPure()) elseif f === Core._hasmethod return _hasmethod_tfunc(interp, argtypes, sv) end @@ -3086,6 +3087,14 @@ function propagate_to_error_handler!(frame::InferenceState, currpc::Int, W::BitS end end +function update_cycle_worklists!(callback, frame::InferenceState) + for (caller, caller_pc) in frame.cycle_backedges + if callback(caller, caller_pc) + push!(caller.ip, block_for_inst(caller.cfg, caller_pc)) + end + end +end + # make as much progress on `frame` as possible (without handling cycles) function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) @assert !is_inferred(frame) @@ -3204,11 +3213,9 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) elseif isa(stmt, ReturnNode) rt = abstract_eval_value(interp, stmt.val, currstate, frame) if update_bestguess!(interp, frame, currstate, rt) - for (caller, caller_pc) in frame.cycle_backedges - if caller.ssavaluetypes[caller_pc] !== Any - # no reason to revisit if that call-site doesn't affect the final result - push!(caller.ip, block_for_inst(caller.cfg, caller_pc)) - end + update_cycle_worklists!(frame) do caller::InferenceState, caller_pc::Int + # no reason to revisit if that call-site doesn't affect the final result + return caller.ssavaluetypes[caller_pc] !== Any end end ssavaluetypes[frame.currpc] = Any @@ -3231,11 +3238,11 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) if cur_hand == 0 if !⊑(𝕃ₚ, exct, frame.exc_bestguess) frame.exc_bestguess = tmerge(𝕃ₚ, frame.exc_bestguess, exct) - for (caller, caller_pc) in frame.cycle_backedges - handler = caller.handler_at[caller_pc][1] - if (handler == 0 ? caller.exc_bestguess : caller.handlers[handler].exct) !== Any - push!(caller.ip, block_for_inst(caller.cfg, caller_pc)) - end + update_cycle_worklists!(frame) do caller::InferenceState, caller_pc::Int + caller_handler = caller.handler_at[caller_pc][1] + caller_exct = caller_handler == 0 ? + caller.exc_bestguess : caller.handlers[caller_handler].exct + return caller_exct !== Any end end else diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 5b414f8786e98..b1c3fddc3df6e 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -51,7 +51,7 @@ function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, irsv::IRIn return RTEffects(rt, exct, effects) end -function kill_block!(ir, bb) +function kill_block!(ir::IRCode, bb::Int) # Kill the entire block stmts = ir.cfg.blocks[bb].stmts for bidx = stmts @@ -64,7 +64,6 @@ function kill_block!(ir, bb) return end - function update_phi!(irsv::IRInterpretationState, from::Int, to::Int) ir = irsv.ir if length(ir.cfg.blocks[to].preds) == 0 diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 55026e54f777d..de296089f43be 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -503,6 +503,11 @@ function adjust_effects(sv::InferenceState) return ipo_effects end +function refine_exception_type(@nospecialize(exc_bestguess), ipo_effects::Effects) + ipo_effects.nothrow && return Bottom + return exc_bestguess +end + # inference completed on `me` # update the MethodInstance function finish(me::InferenceState, interp::AbstractInterpreter) @@ -539,8 +544,8 @@ function finish(me::InferenceState, interp::AbstractInterpreter) end me.result.valid_worlds = me.valid_worlds me.result.result = bestguess - me.result.ipo_effects = me.ipo_effects = adjust_effects(me) - me.result.exc_result = exc_bestguess + ipo_effects = me.result.ipo_effects = me.ipo_effects = adjust_effects(me) + me.result.exc_result = me.exc_bestguess = refine_exception_type(me.exc_bestguess, ipo_effects) if limited_ret # a parent may be cached still, but not this intermediate work: @@ -862,12 +867,13 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize isinferred = is_inferred(frame) edge = isinferred ? mi : nothing effects = isinferred ? frame.result.ipo_effects : adjust_effects(Effects(), method) # effects are adjusted already within `finish` for ipo_effects + exc_bestguess = refine_exception_type(frame.exc_bestguess, effects) # propagate newly inferred source to the inliner, allowing efficient inlining w/o deserialization: # note that this result is cached globally exclusively, we can use this local result destructively volatile_inf_result = isinferred && let inferred_src = result.src isa(inferred_src, CodeInfo) && (is_inlineable(inferred_src) || force_inline) end ? VolatileInferenceResult(result) : nothing - return EdgeCallResult(frame.bestguess, frame.exc_bestguess, edge, effects, volatile_inf_result) + return EdgeCallResult(frame.bestguess, exc_bestguess, edge, effects, volatile_inf_result) elseif frame === true # unresolvable cycle return EdgeCallResult(Any, Any, nothing, Effects()) @@ -875,7 +881,9 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize # return the current knowledge about this cycle frame = frame::InferenceState update_valid_age!(caller, frame.valid_worlds) - return EdgeCallResult(frame.bestguess, frame.exc_bestguess, nothing, adjust_effects(Effects(), method)) + effects = adjust_effects(Effects(), method) + exc_bestguess = refine_exception_type(frame.exc_bestguess, effects) + return EdgeCallResult(frame.bestguess, exc_bestguess, nothing, effects) end function cached_return_type(code::CodeInstance) diff --git a/base/reflection.jl b/base/reflection.jl index 304f7a4344fb2..145a7a9a376d5 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1692,15 +1692,45 @@ function code_ircode_by_type( return asts end +function _builtin_return_type(interp::Core.Compiler.AbstractInterpreter, + @nospecialize(f::Core.Builtin), @nospecialize(types)) + argtypes = Any[to_tuple_type(types).parameters...] + rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) + return Core.Compiler.widenconst(rt) +end + +function _builtin_effects(interp::Core.Compiler.AbstractInterpreter, + @nospecialize(f::Core.Builtin), @nospecialize(types)) + argtypes = Any[to_tuple_type(types).parameters...] + rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) + return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, argtypes, rt) +end + +check_generated_context(world::UInt) = + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") """ - Base.return_types(f::Function, types::DataType=default_tt(f); - world::UInt=get_world_counter(), interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) + Base.return_types( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> rts::Vector{Any} Return a list of possible return types for a given function `f` and argument types `types`. The list corresponds to the results of type inference on all the possible method match candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `rts::Vector{Any}`: The list of return types that are figured out by inference on + methods matching with the given `f` and `types`. The list's order matches the order + returned by `methods(f, types)`. + # Example ```julia @@ -1722,25 +1752,22 @@ julia> Base.return_types(sum, (Union{Vector{Int},UnitRange{Int}},)) ``` !!! warning - The `return_types` function should not be used from generated functions; + The `Base.return_types` function should not be used from generated functions; doing so will result in an error. """ function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)); world::UInt=get_world_counter(), interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && - error("code reflection cannot be used from generated functions") + check_generated_context(world) if isa(f, Core.OpaqueClosure) _, rt = only(code_typed_opaque_closure(f)) return Any[rt] end - if isa(f, Core.Builtin) - argtypes = Any[to_tuple_type(types).parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) - return Any[Core.Compiler.widenconst(rt)] + rt = _builtin_return_type(interp, f, types) + return Any[rt] end - rts = [] + rts = Any[] tt = signature_type(f, types) matches = _methods_by_ftype(tt, #=lim=#-1, world)::Vector for match in matches @@ -1752,35 +1779,220 @@ function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)); end """ - infer_effects(f, types=default_tt(f); world=get_world_counter(), interp=Core.Compiler.NativeInterpreter(world)) + Base.infer_exception_types( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> excts::Vector{Any} -Compute the `Effects` of a function `f` with argument types `types`. The `Effects` represents the computational effects of the function call, such as whether it is free of side effects, guaranteed not to throw an exception, guaranteed to terminate, etc. The `world` and `interp` arguments specify the world counter and the native interpreter to use for the analysis. +Return a list of possible exception types for a given function `f` and argument types `types`. +The list corresponds to the results of type inference on all the possible method match +candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). +It works like [`Base.return_types`](@ref), but it infers the exception types instead of the return types. # Arguments - `f`: The function to analyze. - `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. - `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. -- `interp` (optional): The native interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. # Returns -- `effects::Effects`: The computed effects of the function call. +- `excts::Vector{Any}`: The list of exception types that are figured out by inference on + methods matching with the given `f` and `types`. The list's order matches the order + returned by `methods(f, types)`. # Example ```julia -julia> function foo(x) - y = x * 2 - return y - end; +julia> throw_if_number(::Number) = error("number is given"); + +julia> throw_if_number(::Any) = nothing; + +julia> Base.infer_exception_types(throw_if_number, (Int,)) +1-element Vector{Any}: + ErrorException + +julia> methods(throw_if_number, (Any,)) +# 2 methods for generic function "throw_if_number" from Main: + [1] throw_if_number(x::Number) + @ REPL[1]:1 + [2] throw_if_number(::Any) + @ REPL[2]:1 + +julia> Base.infer_exception_types(throw_if_number, (Any,)) +2-element Vector{Any}: + ErrorException # the result of inference on `throw_if_number(::Number)` + Union{} # the result of inference on `throw_if_number(::Any)` +``` + +!!! warning + The `Base.infer_exception_types` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_exception_types(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return Any[Any] # TODO + end + if isa(f, Core.Builtin) + effects = _builtin_effects(interp, f, types) + exct = Core.Compiler.is_nothrow(effects) ? Union{} : Any + return Any[exct] + end + excts = Any[] + tt = signature_type(f, types) + matches = _methods_by_ftype(tt, #=lim=#-1, world)::Vector + for match in matches + match = match::Core.MethodMatch + frame = Core.Compiler.typeinf_frame(interp, match, #=run_optimizer=#false) + if frame === nothing + exct = Any + else + exct = Core.Compiler.widenconst(frame.result.exc_result) + end + push!(excts, exct) + end + return excts +end + +_may_throw_methoderror(matches#=::Core.Compiler.MethodLookupResult=#) = + matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.matches) + +""" + Base.infer_exception_type( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> exct::Type + +Returns the type of exception potentially thrown by the function call specified by `f` and `types`. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `exct::Type`: The inferred type of exception that can be thrown by the function call + specified by the given call signature. -julia> effects = Base.infer_effects(foo, (Int,)) +!!! note + Note that, different from [`Base.infer_exception_types`](@ref), this doesn't give you the list + exception types for every possible matching method with the given `f` and `types`. + It provides a single exception type, taking into account all potential outcomes of + any function call entailed by the given signature type. + +# Example + +```julia +julia> f1(x) = x * 2; + +julia> Base.infer_exception_type(f1, (Int,)) +Union{} +``` + +The exception inferred as `Union{}` indicates that `f1(::Int)` will not throw any exception. + +```julia +julia> f2(x::Int) = x * 2; + +julia> Base.infer_exception_type(f2, (Integer,)) +MethodError +``` + +This case is pretty much the same as with `f1`, but there's a key difference to note. For +`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. +Because of this, taking into account the chance of the method error entailed by the call +signature, the exception type is widened to `MethodError`. + +!!! warning + The `Base.infer_exception_type` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_exception_type(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return Any # TODO + end + if isa(f, Core.Builtin) + effects = _builtin_effects(interp, f, types) + return Core.Compiler.is_nothrow(effects) ? Union{} : Any + end + tt = signature_type(f, types) + matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) + if matches === nothing + # unanalyzable call, i.e. the interpreter world might be newer than the world where + # the `f` is defined, return the unknown exception type + return Any + end + exct = Union{} + if _may_throw_methoderror(matches) + # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. + exct = Core.Compiler.tmerge(exct, MethodError) + end + for match in matches.matches + match = match::Core.MethodMatch + frame = Core.Compiler.typeinf_frame(interp, match, #=run_optimizer=#false) + frame === nothing && return Any + exct = Core.Compiler.tmerge(exct, Core.Compiler.widenconst(frame.result.exc_result)) + end + return exct +end + +""" + Base.infer_effects( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> effects::Effects + +Returns the possible computation effects of the function call specified by `f` and `types`. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `effects::Effects`: The computed effects of the function call specified by the given call signature. + See the documentation of [`Effects`](@ref Core.Compiler.Effects) or [`Base.@assume_effects`](@ref) + for more information on the various effect properties. + +!!! note + Note that, different from [`Base.return_types`](@ref), this doesn't give you the list + effect analysis results for every possible matching method with the given `f` and `types`. + It provides a single effect, taking into account all potential outcomes of any function + call entailed by the given signature type. + +# Example + +```julia +julia> f1(x) = x * 2; + +julia> Base.infer_effects(f1, (Int,)) (+c,+e,+n,+t,+s,+m,+i) ``` -This function will return an `Effects` object with information about the computational effects of the function `foo` when called with an `Int` argument. See the documentation for `Effects` for more information on the various effect properties. +This function will return an `Effects` object with information about the computational +effects of the function `f1` when called with an `Int` argument. + +```julia +julia> f2(x::Int) = x * 2; + +julia> Base.infer_effects(f2, (Integer,)) +(+c,+e,!n,+t,+s,+m,+i) +``` + +This case is pretty much the same as with `f1`, but there's a key difference to note. For +`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. +Because of this, taking into account the chance of the method error entailed by the call +signature, the `:nothrow` bit gets tainted. !!! warning - The `infer_effects` function should not be used from generated functions; + The `Base.infer_effects` function should not be used from generated functions; doing so will result in an error. # See Also @@ -1790,13 +2002,9 @@ This function will return an `Effects` object with information about the computa function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); world::UInt=get_world_counter(), interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && - error("code reflection cannot be used from generated functions") + check_generated_context(world) if isa(f, Core.Builtin) - types = to_tuple_type(types) - argtypes = Any[types.parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) - return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, argtypes, rt) + return _builtin_effects(interp, f, types) end tt = signature_type(f, types) matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) @@ -1806,7 +2014,7 @@ function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); return Core.Compiler.Effects() end effects = Core.Compiler.EFFECTS_TOTAL - if matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.matches) + if _may_throw_methoderror(matches) # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. effects = Core.Compiler.Effects(effects; nothrow=false) end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index b75e5e5fe87f0..8cdee8baa90e6 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -5558,3 +5558,32 @@ function foo_typed_throw_metherr() return 1 end @test Base.return_types(foo_typed_throw_metherr) |> only === Float64 + +# using `exct` information if `:nothrow` is proven +Base.@assume_effects :nothrow function sin_nothrow(x::Float64) + x == Inf && return zero(x) + return sin(x) +end +@test Base.infer_exception_type(sin_nothrow, (Float64,)) == Union{} +@test Base.return_types((Float64,)) do x + try + return sin_nothrow(x) + catch err + return err + end +end |> only === Float64 +# for semi-concrete interpretation result too +Base.@constprop :aggressive function sin_maythrow(x::Float64, maythrow::Bool) + if maythrow + return sin(x) + else + return @noinline sin_nothrow(x) + end +end +@test Base.return_types((Float64,)) do x + try + return sin_maythrow(x, false) + catch err + return err + end +end |> only === Float64 diff --git a/test/reflection.jl b/test/reflection.jl index 7df6a76dfd0ff..87f16deacdded 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -1047,6 +1047,33 @@ ambig_effects_test(a, b) = 1 @test (Base.infer_effects(Core.Intrinsics.mul_int, ()); true) # `intrinsic_effects` shouldn't throw on empty `argtypes` end +@testset "infer_exception_type[s]" begin + # generic functions + @test Base.infer_exception_type(issue41694, (Int,)) == only(Base.infer_exception_types(issue41694, (Int,))) == ErrorException + @test Base.infer_exception_type((Int,)) do x + issue41694(x) + end == Base.infer_exception_types((Int,)) do x + issue41694(x) + end |> only == ErrorException + @test Base.infer_exception_type(issue41694) == only(Base.infer_exception_types(issue41694)) == ErrorException # use `default_tt` + let excts = Base.infer_exception_types(maybe_effectful, (Any,)) + @test any(==(Any), excts) + @test any(==(Union{}), excts) + end + @test Base.infer_exception_type(maybe_effectful, (Any,)) == Any + # `infer_exception_type` should account for MethodError + @test Base.infer_exception_type(issue41694, (Float64,)) == MethodError # definitive dispatch error + @test Base.infer_exception_type(issue41694, (Integer,)) == Union{MethodError,ErrorException} # possible dispatch error + @test Base.infer_exception_type(f_no_methods) == MethodError # no possible matching methods + @test Base.infer_exception_type(ambig_effects_test, (Int,Int)) == MethodError # ambiguity error + @test Base.infer_exception_type(ambig_effects_test, (Int,Any)) == MethodError # ambiguity error + # builtins + @test Base.infer_exception_type(typeof, (Any,)) === only(Base.infer_exception_types(typeof, (Any,))) === Union{} + @test Base.infer_exception_type(===, (Any,Any)) === only(Base.infer_exception_types(===, (Any,Any))) === Union{} + @test (Base.infer_exception_type(setfield!, ()); Base.infer_exception_types(setfield!, ()); true) # `infer_exception_type[s]` shouldn't throw on empty `argtypes` + @test (Base.infer_exception_type(Core.Intrinsics.mul_int, ()); Base.infer_exception_types(Core.Intrinsics.mul_int, ()); true) # `infer_exception_type[s]` shouldn't throw on empty `argtypes` +end + @test Base._methods_by_ftype(Tuple{}, -1, Base.get_world_counter()) == Any[] @test length(methods(Base.Broadcast.broadcasted, Tuple{Any, Any, Vararg})) > length(methods(Base.Broadcast.broadcasted, Tuple{Base.Broadcast.BroadcastStyle, Any, Vararg})) >=