What an OTP callback returns, and whether it kept the means to reply.
handle_call/3 may answer immediately with {:reply, value, state}, or
defer by returning {:noreply, state} and calling GenServer.reply/2
later. Deferring is a promise, and the only thing that can discharge it is
the from term the callback was handed — an opaque {pid, tag} that
exists nowhere else.
So a handle_call/3 clause that returns {:noreply, _} while never
reading its from parameter has made a promise it cannot keep, no matter
what the rest of the system does. The caller blocks for the full
GenServer.call/3 timeout and then exits.
Approach
Both halves are visible in bytecode without dataflow.
A callback's return shape is a tuple built into {x, 0} immediately
before return, and its tag is a literal atom:
{:put_tuple2, {:x, 0}, {:list, [atom: :noreply, x: 2]}}
:returnAnd from is argument 1, so it arrives in {x, 1}. Reading it is either
a mention of that register or a call of arity two or more, because calls
take their arguments positionally: a body that passes from straight
through, as publish(payload, from, state) does, compiles to no move at
all and mentions the register nowhere.
Which sites drop from is then a reachability question — walk the
control-flow graph from the entry, refuse to pass through any instruction
that reads from, and see which {:noreply, _} sites remain. Per site,
not per function: handle_call/3 compiles every clause into one function,
and asking function-wide lets a clause that defers correctly vouch for one
that does not.
Both directions of imprecision are toward silence. A tail call hides the
return shape, so no tag is recorded; a write to {x, 1} counts as a read,
so a clobbered register looks retained; and any two-argument call counts,
so a callback that merely passes its arguments along is never reported.
Emitted facts
callback_return(id, func, callback, tag)— a literal return tagcallback_stop_reason(id, func, reason)— the reason of a{:stop, reason, ...}return when it is a literal atom or a{:shutdown, term}literalcallback_timeout(id, func, callback, timeout_ms)— the literal integer timeout of a{:ok, state, ms},{:noreply, state, ms}or{:reply, reply, state, ms}returncallback_drops_from(id, func)— a{:noreply, _}site some execution reaches without ever having readfrom
A whole return folded into one literal — {:ok, %{}, 0} with a
constant state compiles to a single move of the tuple into {x, 0} —
is read the same way as a tuple built in place.