Fix all 9 spec test failures: Env scope chain, IO detection, offline mutation
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m11s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m11s
- env.py: Add MergedEnv with dual-parent lookup (primary for set!, secondary for reads), add dict-compat methods to Env - platform_py.py: make_lambda stores env reference (no copy), env_merge uses MergedEnv for proper set! propagation, ancestor detection prevents unbounded chains in TCO recursion, sf_set_bang walks scope chain - types.py: Component/Island io_refs defaults to None (not computed) instead of empty set, so component-pure? falls through to scan - run.py: Test env uses Env class, mock execute-action calls SX lambdas via _call_sx instead of direct Python call Spec tests: 320/320 (was 311/320) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ class Env:
|
||||
bindings: dict[str, Any] | None = None,
|
||||
parent: Env | None = None,
|
||||
):
|
||||
self._bindings: dict[str, Any] = bindings or {}
|
||||
self._bindings: dict[str, Any] = {} if bindings is None else bindings
|
||||
self._parent = parent
|
||||
|
||||
# -- lookup -------------------------------------------------------------
|
||||
@@ -46,12 +46,30 @@ class Env:
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
return self.lookup(name)
|
||||
|
||||
def __setitem__(self, name: str, value: Any) -> None:
|
||||
"""Set *name* in the **current** scope (like ``define``)."""
|
||||
self._bindings[name] = value
|
||||
|
||||
def get(self, name: str, default: Any = None) -> Any:
|
||||
try:
|
||||
return self.lookup(name)
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def update(self, other: dict[str, Any] | Env) -> None:
|
||||
"""Merge *other*'s bindings into the **current** scope."""
|
||||
if isinstance(other, Env):
|
||||
self._bindings.update(other._bindings)
|
||||
else:
|
||||
self._bindings.update(other)
|
||||
|
||||
def keys(self):
|
||||
"""All keys visible from this scope (current + parents)."""
|
||||
return self.to_dict().keys()
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.to_dict())
|
||||
|
||||
# -- mutation -----------------------------------------------------------
|
||||
|
||||
def define(self, name: str, value: Any) -> None:
|
||||
@@ -74,7 +92,7 @@ class Env:
|
||||
|
||||
def extend(self, bindings: dict[str, Any] | None = None) -> Env:
|
||||
"""Return a child environment."""
|
||||
return Env(bindings or {}, parent=self)
|
||||
return Env({} if bindings is None else bindings, parent=self)
|
||||
|
||||
# -- conversion ---------------------------------------------------------
|
||||
|
||||
@@ -95,3 +113,58 @@ class Env:
|
||||
depth += 1
|
||||
p = p._parent
|
||||
return f"<Env depth={depth} keys={keys}>"
|
||||
|
||||
|
||||
class MergedEnv(Env):
|
||||
"""Env with two parent chains: primary (closure) and secondary (caller).
|
||||
|
||||
Reads walk: local bindings → primary chain → secondary chain.
|
||||
set! walks: local bindings → primary chain (skips secondary).
|
||||
This allows set! to modify variables in the defining scope (closure)
|
||||
without being confused by overlay copies from the calling scope.
|
||||
"""
|
||||
|
||||
__slots__ = ("_secondary",)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
bindings: dict[str, Any] | None = None,
|
||||
primary: Env | None = None,
|
||||
secondary: Env | None = None,
|
||||
):
|
||||
super().__init__(bindings, parent=primary)
|
||||
self._secondary = secondary
|
||||
|
||||
def lookup(self, name: str) -> Any:
|
||||
try:
|
||||
return super().lookup(name)
|
||||
except KeyError:
|
||||
if self._secondary is not None:
|
||||
return self._secondary.lookup(name)
|
||||
raise
|
||||
|
||||
def __contains__(self, name: str) -> bool:
|
||||
if super().__contains__(name):
|
||||
return True
|
||||
if self._secondary is not None:
|
||||
return name in self._secondary
|
||||
return False
|
||||
|
||||
def get(self, name: str, default: Any = None) -> Any:
|
||||
try:
|
||||
return self.lookup(name)
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
if self._secondary is not None:
|
||||
d = self._secondary.to_dict()
|
||||
else:
|
||||
d = {}
|
||||
if self._parent is not None:
|
||||
d.update(self._parent.to_dict())
|
||||
d.update(self._bindings)
|
||||
return d
|
||||
|
||||
def extend(self, bindings: dict[str, Any] | None = None) -> Env:
|
||||
return Env(bindings or {}, parent=self)
|
||||
|
||||
Reference in New Issue
Block a user