Split env-bind! from env-set!: fix lexical scoping and closures
Two fundamental environment bugs fixed: 1. env-set! was used for both binding creation (let, define, params) and mutation (set!). Binding creation must NOT walk the scope chain — it should set on the immediate env. Only set! should walk. Fix: introduce env-bind! for all binding creation. env-set! now exclusively means "mutate existing binding, walk scope chain". Changed across spec (eval.sx, cek.sx, render.sx) and all web adapters (dom, html, sx, async, boot, orchestration, forms). 2. makeLambda/makeComponent/makeMacro/makeIsland used merge(env) to flatten the closure into a plain object, destroying the prototype chain. This meant set! inside closures couldn't reach the original binding — it modified a snapshot copy instead. Fix: store env directly as closure (no merge). The prototype chain is preserved, so set! walks up to the original scope. Tests: 499/516 passing (96.7%), up from 485/516. Fixed: define self-reference, let scope isolation, set! through closures, counter-via-closure pattern, recursive functions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1162,12 +1162,12 @@ PLATFORM_JS_PRE = '''
|
||||
function makeSymbol(n) { return new Symbol(n); }
|
||||
function makeKeyword(n) { return new Keyword(n); }
|
||||
|
||||
function makeLambda(params, body, env) { return new Lambda(params, body, merge(env)); }
|
||||
function makeLambda(params, body, env) { return new Lambda(params, body, env); }
|
||||
function makeComponent(name, params, hasChildren, body, env, affinity) {
|
||||
return new Component(name, params, hasChildren, body, merge(env), affinity);
|
||||
return new Component(name, params, hasChildren, body, env, affinity);
|
||||
}
|
||||
function makeMacro(params, restParam, body, env, name) {
|
||||
return new Macro(params, restParam, body, merge(env), name);
|
||||
return new Macro(params, restParam, body, env, name);
|
||||
}
|
||||
function makeThunk(expr, env) { return new Thunk(expr, env); }
|
||||
|
||||
@@ -1257,7 +1257,7 @@ PLATFORM_JS_PRE = '''
|
||||
|
||||
// Island platform
|
||||
function makeIsland(name, params, hasChildren, body, env) {
|
||||
return new Island(name, params, hasChildren, body, merge(env));
|
||||
return new Island(name, params, hasChildren, body, env);
|
||||
}
|
||||
|
||||
// JSON / dict helpers for island state serialization
|
||||
@@ -1272,6 +1272,11 @@ PLATFORM_JS_PRE = '''
|
||||
|
||||
function envHas(env, name) { return name in env; }
|
||||
function envGet(env, name) { return env[name]; }
|
||||
function envBind(env, name, val) {
|
||||
// Direct property set — creates or overwrites on THIS env only.
|
||||
// Used by let, define, defcomp, lambda param binding.
|
||||
env[name] = val;
|
||||
}
|
||||
function envSet(env, name, val) {
|
||||
// Walk prototype chain to find where the variable is defined (for set!)
|
||||
var obj = env;
|
||||
|
||||
Reference in New Issue
Block a user