Skip to content

Record include(mapexpr) functions#62176

Open
timholy wants to merge 1 commit into
masterfrom
teh/mapexpr
Open

Record include(mapexpr) functions#62176
timholy wants to merge 1 commit into
masterfrom
teh/mapexpr

Conversation

@timholy

@timholy timholy commented Jun 19, 2026

Copy link
Copy Markdown
Member

Package cache headers store information needed to determine validity and a cache of source-text. The latter gets consumed by Revise, as a record of what the code was before edits were made. This allows Revise to skip defensively parsing every loaded package. However, there has long been a gap in this system: include(mapexpr, …) calls, which can modify the "effective" source text of the included files. Currently, Revise cannot even determine whether a package has used a non-identity mapexpr function without defensively parsing every included file.

Add Base.include_mapexprs(mod), returning the non-identity mapexpr functions passed to include(mapexpr, …) calls while loading the package rooted at mod, keyed by (including_module, absolute_path), or nothing when there were none.

These functions are recorded into a hidden const binding in the package's own root module as the includes run, so they serialize into the package image and remain available after the package is loaded from its cache.

This lets revision tools (e.g. Revise) re-apply the original transform when an include(mapexpr, …)-ed file is edited. Recording the actual function object is necessary because a mapexpr that captures runtime state cannot be reconstructed by re-parsing the source; the precompile environment is closed and deterministic, so the object is serializable.

Package cache headers store information needed to determine validity and
a cache of source-text. The latter gets consumed by Revise, as a record
of what the code was before edits were made. This allows Revise to skip
defensively parsing every loaded package. However, there has long been a
gap in this system: `include(mapexpr, …)` calls, which can modify the
"effective" source text of the included files. Currently, Revise cannot
even determine whether a package has used a non-identity `mapexpr`
function without defensively parsing every included file.

Add `Base.include_mapexprs(mod)`, returning the non-identity `mapexpr`
functions passed to `include(mapexpr, …)` calls while loading the
package rooted at `mod`, keyed by `(including_module, absolute_path)`,
or `nothing` when there were none.

These functions are recorded into a hidden `const` binding in the
package's own root module as the includes run, so they serialize into
the package image and remain available after the package is loaded from
its cache.

This lets revision tools (e.g. Revise) re-apply the original transform
when an `include(mapexpr, …)`-ed file is edited. Recording the actual
function object is necessary because a `mapexpr` that captures runtime
state cannot be reconstructed by re-parsing the source; the precompile
environment is closed and deterministic, so the object is serializable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@timholy

timholy commented Jun 19, 2026

Copy link
Copy Markdown
Member Author

On my bike ride home I asked myself, "if I wanted to break this, how would I do it?" And unfortunately there is a clear path: piracy. Suppose PkgA has a mapexpr that relies on some Base function foo, with the output Exprs depending on values returned by foo. PkgA does not depend on package Pirate. Pirate, being a bad citizen, redefines Base.foo causing it to return different values for some types that PkgA will pass into foo. Now a user does using Pirate, PkgA. Presto, Revise has the wrong idea about what code actually got serialized in the pkgimage.

This is tough because there is no monotonic world-age in which it works; you'd have to insist that mapexpr be precompiled and somehow call the invalidated version that loaded from the cache file. I can see one viable way to prevent that, however: require that mapexprs be @opaque. Thoughts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant