False edges in the call graph can cause calls to unsupported functions to typecheck and be emitted
#5507 opened on Mar 25, 2026
Description
Given the following module in a dependency:
import gleam/option
@external(javascript, "../gleam_stdlib/gleam/function.mjs", "identity")
pub fn id(a: a) -> a
fn wibble() -> option.Option(Int) {
option.Some(id(42))
}
pub fn option() -> option.Option(Int) {
wibble()
}
calling wibble.option() from the root package type-checks even on the Erlang target, but errors in the erlang compiler with a undefined function wibble/0 error.
option should not be supported on the Erlang target, since wibble calls id, which is Javascript-only. We hit this bug in practice in the Tiramisu release candidate, which primarily targets Javascript, but includes some functions to be used with server-side Lustre and support both targets. I think @renatillas solved this by adding a bunch of @target annotations instead.
What's happening here is that the option.Some call creates a false edge in the call graph to the local option function. When the compiler analyses target support, all functions start out as being supported on all targets. Because of the false edge, wibble and option create a fake circular dependency, so here option is analysed before wibble. It sees wibble is still supported on both targets as that's the default and is emitted in the resulting Erlang file. After, wibble of course is not supported and is not emitted, but option still includes a call to a now missing wibble local function.
~ 💜