Repository navigation
Closures keep the imports of the script that made them (fixes #1193) - #1194
Merged
Merged
Conversation
Since 648c684 ("Remove unnecessary embedded environment from FnPtr"), a closure gets an environment only from its function's entry in the lib, which closures of the main script don't have. Called after the script has finished (a callback that the host keeps and calls later with FnPtr::call), such a closure no longer finds the script's imports: import "m" as m; keep(|| m::who()); fails with ErrorModuleNotFound("m") when the kept closure is called. It worked in 1.26.1. Put back FnPtr::env, set when a closure literal is evaluated as before, and prefer it to the lib's environment when calling. The bare function name as a variable (6f4ed21) keeps env None, so the fix for a module's closure seeing the caller's functions stays. FnPtr is 48 bytes again. Rhai Grain is unchanged: it pools closure literals as constants and never fills the environment, so it still fails on this script. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Grain compiler pools closure literals as constants, and the VM pushed them with env None, so a closure kept by the host and called later with FnPtr::call still failed with ErrorModuleNotFound on the Grain path. Load a FnPtr constant with an environment taken from the VM's global state, as get_literal_value does for the walker. Only programs that use function pointers (Caps::FN_PTR) pay for the check. Also refuse to pool a FnPtr constant that already has an environment, so the compiler leaves it to the walker instead of writing an artifact that loses the environment and fails the same way when read back. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Collaborator
|
There is a test that fails. Probably should have used the |
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1193.
Problem
Since 648c684 ("Remove unnecessary embedded environment from FnPtr"), a closure gets its environment only from its function's entry in the lib, which closures of the main script don't have. A closure kept by the host and called after the script has finished, with
FnPtr::call, no longer finds the script'simports:fails with
ErrorModuleNotFound("m")when the kept closure is called. It worked in 1.26.1. The bisect and the repro are in the issue.Changes
Commit 1: AST walker. Put back
FnPtr::env, set when a closure literal is evaluated (as before 648c684), and prefer it to the lib's environment when calling. The bare function name as a variable (6f4ed21) keepsenv: None, so the fix for a module's closure seeing the caller's functions stays.FnPtris 48 bytes again. Test:test_module_closure_keeps_imports.Commit 2: Grain. The first commit left Grain unchanged, and it hit the same error: the compiler pools closure literals as constants and the VM pushed them with
env: None.FnPtrconstant an environment built from its global state, asget_literal_valuedoes for the walker. It checks only when the program uses function pointers (Caps::FN_PTR), so other programs pay one flag test per constant load.constant_capsrefuses aFnPtrthat already has an environment, so such a constant is not pooled. The compiler leaves it to the walker instead of writing an artifact that loses the environment and fails the same way when read back. (As suggested in the issue discussion.) I could not get anenv: Someconstant into an AST through the public API, since the optimizer rebuilds scope-constant closures asFn("anon$…")calls, so this guard is defensive.a_kept_closure_keeps_the_imports_of_its_script) and a unit test forconstant_caps.Design note
I did not fragment every closure literal into a residual so that the walker sets the environment. That would stop
a.map(|x| ..)and similar from lowering, and the GrainAGENTS.mdsays to prefer instructions over fragments. Setting the environment in the VM needs no new opcode and no wire-format change.Testing
cargo test --testspasses with the default features,internals,grain,bin-features,grain,internals, andinternals,graincombined with each ofno_closure,no_function,no_moduleandno_object,no_index. The library builds without warnings under severalno_*combinations.🤖 Generated with Claude Code