Wiki

The wrap is the caller

When you wrap a script body in an anonymous function to scope runtime options, the wrap becomes the immediate caller of every identity-introspecting check inside it. Any line that asks "who called me?" sees the wrap, not the real top level. Caller-identity checks have to live outside the wrap, even when everything else they coordinate with lives inside.

When to reach for it

A script needs to scope a runtime option locally (zsh's emulate -L, bash's local-with-options pattern, Python's context manager around globals, Lua's environment swap, JS's strict-mode wrap). The natural shape is an immediately-invoked function: define a wrapper, set the option at the top of it, do the work, the function returns, the caller's environment snaps back. Inside the wrap there is also a piece of logic that introspects "am I being autoloaded vs sourced," "am I the top-level entry," "what's the caller's file," and dispatches based on that. The dispatch breaks the moment the wrap goes on and nothing else in the wrap's body explains why.

The shape

Three things are true at the same time.

  1. An IIFE creates a new call frame. Every language with first-class anonymous functions surfaces some "who called me" value: zsh exposes $funcstack[1], bash gives FUNCNAME[0] and BASH_SOURCE[0], Python's inspect.stack() and the if __name__ == "__main__" check, Lua's debug.getinfo, JS's Function.caller and new Error().stack. All of them see the wrapper as the closest frame and the real caller as the next frame up.
  2. Option scoping wants to be the outermost layer. The whole point of the wrap is to scope a runtime option (zsh ksharrays off, bash errexit on, Python locale forced) across every inner statement. If a single statement leaks outside the wrap, the option doesn't apply to it. So the impulse is to put everything inside.
  3. Identity-check dispatches want to be the outermost layer. Autoload mechanisms, "run as main" guards, source-vs-execute detection — these all rely on seeing the real caller context. They are conditional dispatches that decide whether to run the entry point or do nothing. If they run inside the wrap, they read the wrap's identity and the dispatch goes the wrong way every time.

The two goals collide. They cannot both be outermost. The resolution is that one of them is allowed to live inside and the other has to live outside. Option scoping moves inside, because the option only matters for the inner statements. Identity-check dispatch stays outside, because it has to see the real caller context.

The discriminator

Three questions narrow it.

If all three are true, the wrap is the caller and the dispatch will see the wrong value. The fix is to pull the dispatch outside the wrap, leaving the option scope intact for the function definitions and source-time guards inside.

Real applications

clap-rs/clap#6373 (2026-05-11)

clapcomplete's zsh template wraps the generated script in an anonymous IIFE to scope `emulate -L zsh -o noksharrays across all the source-time (( $+functions[..._commands] )) arithmetic checks that otherwise fail under setopt ksharrays`. The natural first move: put the whole script inside the wrap, including the final dispatch:

#compdef name

() {
    emulate -L zsh -o no_ksharrays

    _name() { ... completion body ... }

    # source-time guards
    (( $+functions[_name_commands] )) || _name_commands() { ... }

    if [ "$funcstack[1]" = "_name" ]; then
        _name "$@"
    else
        compdef _name name
    fi
}

The interactive test failed with an empty completion menu. The dispatch fell into the compdef _name name branch every time because $funcstack[1] inside the IIFE was the anonymous function's name, not _name. When the shell autoloaded the file in completion context (which sets $funcstack[1] to _name), the IIFE shadowed that. The if saw the anonymous frame and skipped the entry call.

The two-layer fix: option scope inside, dispatch outside.

#compdef name

() {
    emulate -L zsh -o no_ksharrays

    _name() {
        emulate -L zsh -o no_ksharrays   # for completion-time
        ... completion body ...
    }

    (( $+functions[_name_commands] )) || _name_commands() { ... }
}

if [ "$funcstack[1]" = "_name" ]; then
    _name "$@"
else
    compdef _name name
fi

The IIFE wraps function definitions and source-time arithmetic guards. The dispatch lives at real top level so autoload context is preserved. The entry function gets its own emulate -L because LOCAL_OPTIONS propagates the flip to every subroutine called from the entry, covering the completion call tree under a caller's ksharrays.

What this doesn't replace

When not to use it

Related

Revisit

Add a second real application when the next wrap-shadowing bug fires. Specifically watch for the Python if __name__ == "__main__" case (wrap a module's body in a function for testing isolation, dispatch breaks because __name__ inside any function is the module name not "__main__") and for the bash BASH_SOURCE[0] == $0 source-vs-execute check inside a function. If a non-shell language produces the same dispatch collision through a different identity primitive, split the discriminator section into per-language subsections.