# Why is the return type of the following inplace broadcast operation not inferred?

**URL:** <https://discourse.julialang.org/t/why-is-the-return-type-of-the-following-inplace-broadcast-operation-not-inferred/102449>\
**Category:** Performance\
**Tags:** type-stability\
**Created:** [August 3, 2023, 2:51pm UTC](https://discourse.julialang.org/t/why-is-the-return-type-of-the-following-inplace-broadcast-operation-not-inferred/102449 "2023-08-03T14:51:07Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [August 3, 2023, 2:51pm UTC](https://discourse.julialang.org/t/why-is-the-return-type-of-the-following-inplace-broadcast-operation-not-inferred/102449/1 "2023-08-03T14:51:07Z")

</div>

```julia
julia> @descend_code_warntype (Y -> Y .+= Any[rand(size(Y))][1])(rand(2))
(::var"#3#4")(Y) @ Main REPL[4]:1
┌ Warning: couldn't retrieve source of (::var"#3#4")(Y) @ Main REPL[4]:1
└ @ TypedSyntax ~/.julia/packages/TypedSyntax/vbZHi/src/node.jl:31
Variables
  #self#::Core.Const(var"#3#4"())
  Y::Vector{Float64}

Body::Any
    @ REPL[4]:1 within `#3`
1 ─ %1 = Main.:+::Core.Const(+)
│ %2 = Main.Any::Core.Const(Any)
│ %3 = Main.size(Y)::Tuple{Int64}
│ %4 = Main.rand(%3)::Int64
│ %5 = Base.getindex(%2, %4)::Vector{Any}
│ %6 = Base.getindex(%5, 1)::Any
│ %7 = Base.broadcasted(%1, Y, %6)::Any
│ %8 = Base.materialize!(Y, %7)::Any
└── return %8
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
 • %3 = size(::Vector{Float64})::Tuple{Int64}
   %4 = rand(::Tuple{Int64})::Int64
   %5 = getindex(::Type{Any},::Int64)::Vector{Any}
   %6 = getindex(::Vector{Any},::Int64)::Any
   %7 = broadcasted(::typeof(+),::Vector{Float64},::Any)::Any
   %8 = call → materialize!(::Vector{Float64},::Any)::Any

```

The input array is the destination and is returned, so its type should be known, and the return type of `Base.materialize!` should be known.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [August 3, 2023, 2:56pm UTC](https://discourse.julialang.org/t/why-is-the-return-type-of-the-following-inplace-broadcast-operation-not-inferred/102449/2 "2023-08-03T14:56:36Z")

</div>

We don’t know know what the result of `Base.materialize!(::Vector{Float64}, ::Any)` could be. Someone could have overloaded `Base.materialize!(::Vector{Float64}, ::SomeNewType)` to return anything.

---

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [August 3, 2023, 2:59pm UTC](https://discourse.julialang.org/t/why-is-the-return-type-of-the-following-inplace-broadcast-operation-not-inferred/102449/3 "2023-08-03T14:59:51Z")

</div>

Ah, brainfade

---

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [August 3, 2023, 4:08pm UTC](https://discourse.julialang.org/t/why-is-the-return-type-of-the-following-inplace-broadcast-operation-not-inferred/102449/4 "2023-08-03T16:08:42Z")

</div>

Is it reasonable to change the lowering to include a `return Y` at the end? This should not be breaking, as `materialize!` being the last step is an implementation detail at best. This can help quite a bit in badly inferred code.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [August 3, 2023, 5:10pm UTC](https://discourse.julialang.org/t/why-is-the-return-type-of-the-following-inplace-broadcast-operation-not-inferred/102449/5 "2023-08-03T17:10:18Z")

</div>

That sounds reasonable to me.

```julia
julia> @code_warntype (Y -> (Y .+= Any[rand(size(Y))][1]))(rand(2))
MethodInstance for (::var"#15#16")(::Vector{Float64})
  from (::var"#15#16")(Y) @ Main REPL[7]:1
Arguments
  #self#::Core.Const(var"#15#16"())
  Y::Vector{Float64}
Body::Any
1 ─ %1 = Main.:+::Core.Const(+)
│ %2 = Main.Any::Core.Const(Any)
│ %3 = Main.size(Y)::Tuple{Int64}
│ %4 = Main.rand(%3)::Int64
│ %5 = Base.getindex(%2, %4)::Vector{Any}
│ %6 = Base.getindex(%5, 1)::Any
│ %7 = Base.broadcasted(%1, Y, %6)::Any
│ %8 = Base.materialize!(Y, %7)::Any
└── return %8

julia> @code_warntype (Y -> (Y .+= Any[rand(size(Y))][1]; return Y))(rand(2))
MethodInstance for (::var"#17#18")(::Vector{Float64})
  from (::var"#17#18")(Y) @ Main REPL[8]:1
Arguments
  #self#::Core.Const(var"#17#18"())
  Y::Vector{Float64}
Body::Vector{Float64}
1 ─ %1 = Main.:+::Core.Const(+)
│ %2 = Main.Any::Core.Const(Any)
│ %3 = Main.size(Y)::Tuple{Int64}
│ %4 = Main.rand(%3)::Int64
│ %5 = Base.getindex(%2, %4)::Vector{Any}
│ %6 = Base.getindex(%5, 1)::Any
│ %7 = Base.broadcasted(%1, Y, %6)::Any
│ Base.materialize!(Y, %7)
└── return Y

```

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [August 3, 2023, 9:52pm UTC](https://discourse.julialang.org/t/why-is-the-return-type-of-the-following-inplace-broadcast-operation-not-inferred/102449/6 "2023-08-03T21:52:20Z")

</div>

It seems like `broadcast!`, which is supposed to be equivalent to the in-place dot syntax, handles this by returning the destination array. In the case where it’s a function call in another function, ~~I’m still not entirely sure how the runtime dispatch of~~ `broadcast!` is inferred properly even when I `@noinline` it ~~, I thought it would’ve been an unknown call with unknown return type like with `materialize!`:~~ because the compiler does check the sole method’s return type. You can ruin it by type pirating more applicable methods for the runtime call: `for T in (Bool, Float64, Float32, ComplexF64) @eval Base.broadcast!(f::Tf, dest, A1::Vector{Float64}, As::Vararg{$T, N}) where {Tf, N} = $(zero(T)) end # no methods for the Int example`.

```julia
julia> function test1(a::Vector{Any} = Any[2])
         two = a[begin] # what the original code always results in
         dest = rand(2)
         @noinline broadcast!(+, dest, dest, two)
       end
test1 (generic function with 2 methods)

julia> @code_warntype test1(Any[2])
MethodInstance for test1(::Vector{Any})
  from test1(a::Vector{Any}) in Main at REPL[7]:1
Arguments
  #self#::Core.Const(test1)
  a::Vector{Any}
Locals
  val::Vector{Float64}
  dest::Vector{Float64}
  two::Any
Body::Vector{Float64}
1 ─ %1 = Base.firstindex(a)::Core.Const(1)
│ (two = Base.getindex(a, %1))
│ (dest = Main.rand(2))
│ nothing
│ (val = Main.broadcast!(Main.:+, dest, dest, two))
│ nothing
└── return val

```

compared to

```julia
julia> function test2(a::Vector{Any})
         two = a[begin] # what the original code always results in
         dest = rand(2)
         dest .+= two
       end
test2 (generic function with 1 method)

julia> @code_warntype test2(Any[2])
MethodInstance for test2(::Vector{Any})
  from test2(a::Vector{Any}) in Main at REPL[32]:1
Arguments
  #self#::Core.Const(test2)
  a::Vector{Any}
Locals
  dest::Vector{Float64}
  two::Any
Body::Any
1 ─ %1 = Base.firstindex(a)::Core.Const(1)
│ (two = Base.getindex(a, %1))
│ (dest = Main.rand(2))
│ %4 = dest::Vector{Float64}
│ %5 = Base.broadcasted(Main.:+, dest, two)::Any
│ %6 = Base.materialize!(%4, %5)::Any
└── return %6

```
