Repository navigation
MacroCompiler: nested codeblocks can use LOCALs of the enclosing script/codeblock - #2113
Merged
RobertvanderHulst merged 1 commit intoOct 23, 2026
Merged
Conversation
…script/codeblock
A codeblock inside a macro script was compiled as an isolated DynamicMethod, so
a LOCAL of the script (or a parameter of an outer codeblock) was unknown inside
it and was resolved at runtime as memvar/field ("Variable does not exist").
Scripts had to fall back to PRIVATEs or __LocalPut()/__LocalsClear().
Nested codeblocks now capture such locals like the full compiler does:
- Lookup in a nested binder falls back to the locals of the enclosing binders
(any depth); memvars, fields, constants and arguments keep their existing
late bound resolution.
- A captured local is stored in a StrongBox<T> shared by the outer code and the
codeblock, so assignments are visible on both sides.
- A capturing codeblock gets the signature (Codeblock[], object[] env, T[] args);
every evaluation of the codeblock literal creates a new codeblock bound to the
boxes (MacroClosure.Bind), so recursion and stored codeblocks stay correct.
- Codeblock parameters are read from the args array by position instead of by
IL local index, catch variables are declared inside the catch block and the
FOREACH dispose code uses EmitGet, so these work when captured too.
Codeblocks that do not use outer locals are compiled exactly as before.
The former "#ifdef FAIL" script test is enabled and more tests are added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closed
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.
Problem
In a macro script, a codeblock cannot see the LOCALs of the script:
Nested codeblocks are compiled as isolated
DynamicMethods, sonLis unknown inside the block and is resolved at runtime as memvar/field. Scripts have to use PRIVATEs or__LocalPut()/__LocalsClear()instead. This case was already present inScriptTests.prgunder#ifdef FAIL.Change
Nested codeblocks now capture locals of the enclosing script or codeblock, like the full compiler does:
Binder.CaptureLocal). Memvars, fields, constants and arguments keep their existing late-bound resolution.StrongBox<T>that the outer code and the codeblock share. Assignments inside the block are visible outside and vice versa.(Codeblock[] nested, object[] env, T[] args). Each evaluation of the codeblock literal creates a new codeblock bound to the current boxes (ClosureTemplate/MacroClosure.Bind), so recursion and stored codeblocks behave correctly.EmitGetCodeblocks that don't use outer locals are compiled exactly as before.
Tests
ScriptTests.prg: the former#ifdef FAILtest is enabled. New tests cover assigning a captured local, a parameter captured two levels deep, and a codeblock that sees later changes of the local.Harness: I also checked with a C# harness, in the VO and Core dialects, through both
Compile(object) andCompileCodeblock(usual). The harness covers:__LocalPutas regression checksAll cases fail with "Variable does not exist" before the change and pass after it.
Real application: the change was tried in a real application that runs scripts through
GetScriptCompiler()+CompileCodeblock().Side note, not changed here:
AEval()cannot be called from macros or scripts at all. It fails withXM0205 No suitable overload, with or without this change. That is being looked at separately.🤖 Generated with Claude Code