Documentation
¶
Overview ¶
Package environment provides variable binding and scoping for the Scheme compiler.
The environment system manages the relationship between variables and values across:
- Lexical scoping via parent chain traversal
- Phase separation (Runtime, Expand, Compile)
- Hygienic macros via Flatt's "sets of scopes" model
- Per-instance syntax interning for consistent syntax identity
- R7RS library import/export mappings
Architecture ¶
Each Wile VM instance owns a Namespace that provides:
- Syntax interning (thread-safe, per-instance)
- Phase registry for accessing phase-specific environments
- The runtime EnvironmentFrame tree
EnvironmentFrame nodes form the lexical scope chain, each containing:
- Local bindings (LocalEnvironmentFrame) for lambda params and let-bound vars
- Global bindings (GlobalEnvironmentFrame) for top-level defines
- Parent pointer for enclosing scopes
Binding Lookup ¶
EnvironmentFrame.GetBinding performs two-phase lookup:
- Local phase: traverse local bindings up the parent chain
- Global phase: search global bindings up the parent chain
When scopes are provided, EnvironmentFrame.GetBinding adds hygiene awareness, using [Scope] sets to match identifiers according to Flatt's algorithm with maximal binding selection.
Index ¶
- Constants
- func AmbientScopes() []*syntax.Scope
- func SameBinding(a, b *Binding) bool
- type Binding
- func (p *Binding) BindingType() BindingType
- func (p *Binding) Doc() string
- func (p *Binding) InlineHOFName() string
- func (p *Binding) InlineHOFParam() int
- func (p *Binding) IsCaptureSafe() bool
- func (p *Binding) IsImported() bool
- func (p *Binding) IsStable() bool
- func (p *Binding) Meta() *BindingMeta
- func (p *Binding) Origin() *OriginRef
- func (p *Binding) Scopes() []*syntax.Scope
- func (p *Binding) SetValue(value values.Value)
- func (p *Binding) Source() *syntax.SourceContext
- func (p *Binding) UpdateMeta(fn func(*BindingMeta) bool) bool
- func (p *Binding) Value() values.Value
- type BindingID
- type BindingMeta
- type BindingRef
- type BindingRefKind
- type BindingType
- type EngineServices
- type EnvironmentFrame
- func (p *EnvironmentFrame) AtPhase(phase Phase) *EnvironmentFrame
- func (p *EnvironmentFrame) Compile() *EnvironmentFrame
- func (p *EnvironmentFrame) DefineOwnGlobal(key *values.Symbol, bt BindingType, scopes []*syntax.Scope, v values.Value) (*GlobalIndex, error)
- func (p *EnvironmentFrame) DeleteOwnGlobal(sym *values.Symbol, scopes []*syntax.Scope) bool
- func (p *EnvironmentFrame) EnsureLocalBinding(key *values.Symbol, bt BindingType) (*LocalIndex, bool)
- func (p *EnvironmentFrame) EqualTo(value values.Value) bool
- func (p *EnvironmentFrame) Expand() *EnvironmentFrame
- func (p *EnvironmentFrame) FileResolver() FileResolver
- func (p *EnvironmentFrame) GetBinding(key *values.Symbol, q syntax.ScopeSet) *Binding
- func (p *EnvironmentFrame) GetGlobalBinding(key *GlobalIndex) *Binding
- func (p *EnvironmentFrame) GetGlobalIndex(key *values.Symbol) *GlobalIndex
- func (p *EnvironmentFrame) GetGlobalIndexAcrossPhases(key *values.Symbol, scopes []*syntax.Scope) *GlobalIndex
- func (p *EnvironmentFrame) GetGlobalIndexFromLibraryScopes(key *values.Symbol, scopes []*syntax.Scope) *GlobalIndex
- func (p *EnvironmentFrame) GetGlobalIndexWithScopes(key *values.Symbol, q syntax.ScopeSet) *GlobalIndex
- func (p *EnvironmentFrame) GetLocalBinding(li *LocalIndex) *Binding
- func (p *EnvironmentFrame) GetLocalBindingByIndex(i int) *Binding
- func (p *EnvironmentFrame) GetLocalBindingBySlotDepth(slot, depth int) *Binding
- func (p *EnvironmentFrame) GetLocalIndex(key *values.Symbol, q syntax.ScopeSet) *LocalIndex
- func (p *EnvironmentFrame) GlobalEnvironment() *GlobalEnvironmentFrame
- func (p *EnvironmentFrame) HasLocalVariableBinding(sym *values.Symbol, q syntax.ScopeSet) bool
- func (p *EnvironmentFrame) InitApplyFrame(dst *EnvironmentFrame) int
- func (p *EnvironmentFrame) InitApplyFrameWithParent(dst *EnvironmentFrame, parent *EnvironmentFrame) int
- func (p *EnvironmentFrame) IsNamespaceRuntime() bool
- func (p *EnvironmentFrame) IsOwnerRoot() bool
- func (p *EnvironmentFrame) IsTopLevel() bool
- func (p *EnvironmentFrame) IsVoid() bool
- func (p *EnvironmentFrame) LibraryRegistry() LibrarySearcher
- func (p *EnvironmentFrame) LocalBindingsSlice() []Binding
- func (p *EnvironmentFrame) LocalEnvironment() *LocalEnvironmentFrame
- func (p *EnvironmentFrame) MaybeCreateLocalBinding(key *values.Symbol, bt BindingType, scopes []*syntax.Scope, ...) (*LocalIndex, bool)
- func (p *EnvironmentFrame) MaybeCreateOwnGlobalBinding(key *values.Symbol, bt BindingType, scopes []*syntax.Scope) (*GlobalIndex, bool)
- func (p *EnvironmentFrame) MutableRuntime() *EnvironmentFrame
- func (p *EnvironmentFrame) MutableRuntimeOrNil() *EnvironmentFrame
- func (p *EnvironmentFrame) Namespace() *Namespace
- func (p *EnvironmentFrame) NewApplyFrame() *EnvironmentFrame
- func (p *EnvironmentFrame) NextPhase() *EnvironmentFrame
- func (p *EnvironmentFrame) NextPhaseChecked(base Phase) (*EnvironmentFrame, error)
- func (p *EnvironmentFrame) OwnGlobalIndex(key *values.Symbol, q syntax.ScopeSet) *GlobalIndex
- func (p *EnvironmentFrame) Parent() *EnvironmentFrame
- func (p *EnvironmentFrame) PhaseLevel() Phase
- func (p *EnvironmentFrame) PreAllocateBindings(n int)
- func (p *EnvironmentFrame) PresentPhases() []Phase
- func (p *EnvironmentFrame) ResetForPool()
- func (p *EnvironmentFrame) ResolveBindingID(key *values.Symbol, q syntax.ScopeSet) (BindingID, bool)
- func (p *EnvironmentFrame) ResolveBindingRef(key *values.Symbol, q syntax.ScopeSet) BindingRef
- func (p *EnvironmentFrame) Runtime() *EnvironmentFrame
- func (p *EnvironmentFrame) SchemeString() string
- func (p *EnvironmentFrame) SealedWriteViewAt(phase Phase) *EnvironmentFrame
- func (p *EnvironmentFrame) SetDeferredGlobalValue(gi *GlobalIndex, v values.Value) error
- func (p *EnvironmentFrame) SetFileResolver(resolver FileResolver)
- func (p *EnvironmentFrame) SetGlobalBindingByIndex(i int, bd *Binding)
- func (p *EnvironmentFrame) SetLibraryRegistry(registry LibrarySearcher)
- func (p *EnvironmentFrame) SetLocalValue(li *LocalIndex, v values.Value) error
- func (p *EnvironmentFrame) SetLocalValueBySlotDepth(slot, depth int, v values.Value) error
- func (p *EnvironmentFrame) SetOwnGlobalValue(gi *GlobalIndex, v values.Value) error
- func (p *EnvironmentFrame) TopLevel() *EnvironmentFrame
- func (p *EnvironmentFrame) WritesOwnerRootCoordinates() bool
- type FileResolver
- type GlobalEnvironmentFrame
- func (p *GlobalEnvironmentFrame) AmbientKeysAt(phase Phase) []values.Symbol
- func (p *GlobalEnvironmentFrame) Bindings() []*Binding
- func (p *GlobalEnvironmentFrame) Copy() *GlobalEnvironmentFrame
- func (p *GlobalEnvironmentFrame) CreateGlobalBindingAt(key *values.Symbol, bt BindingType, scopes []*syntax.Scope, phase PhaseKey, ...) (*GlobalIndex, bool)
- func (p *GlobalEnvironmentFrame) DeleteBindingAt(sym *values.Symbol, scopes []*syntax.Scope, phase PhaseKey, sealed bool) bool
- func (p *GlobalEnvironmentFrame) GetOwnGlobalBinding(gi *GlobalIndex) *Binding
- func (p *GlobalEnvironmentFrame) IsSealedBindingAt(key *values.Symbol, q syntax.ScopeSet, phase Phase) bool
- func (p *GlobalEnvironmentFrame) LiveSlots() []NamedSlot
- func (p *GlobalEnvironmentFrame) SealedBindingAt(key *values.Symbol, q syntax.ScopeSet, phase Phase) *Binding
- func (p *GlobalEnvironmentFrame) SealedGlobalIndexAt(key *values.Symbol, q syntax.ScopeSet, phase Phase) *GlobalIndex
- func (p *GlobalEnvironmentFrame) SealedSlots() []NamedSlot
- func (p *GlobalEnvironmentFrame) SetOwnGlobalValue(gi *GlobalIndex, v values.Value) error
- type GlobalIndex
- type InlineHOFTemplateStore
- type LibraryEnvFactory
- type LibrarySearcher
- type LocalEnvironmentFrame
- func (p *LocalEnvironmentFrame) AppendAnonymousSlot() int
- func (p *LocalEnvironmentFrame) Bindings() []Binding
- func (p *LocalEnvironmentFrame) CopyCount() int
- func (p *LocalEnvironmentFrame) EnsureLocalBinding(key *values.Symbol, bt BindingType) (*LocalIndex, bool)
- func (p *LocalEnvironmentFrame) GetLocalBinding(li *LocalIndex) *Binding
- func (p *LocalEnvironmentFrame) GetLocalIndex(key *values.Symbol) *LocalIndex
- func (p *LocalEnvironmentFrame) Keys() map[values.Symbol][]int
- func (p *LocalEnvironmentFrame) MaybeCreateLocalBinding(key *values.Symbol, bt BindingType, scopes []*syntax.Scope, ...) (*LocalIndex, bool)
- func (p *LocalEnvironmentFrame) SetLocalValue(li *LocalIndex, v values.Value) error
- type LocalIndex
- type ModuleInstance
- type NamedSlot
- type Namespace
- func (p *Namespace) AtPhase(phase Phase) *EnvironmentFrame
- func (p *Namespace) AttachModule(path string, target *Namespace) error
- func (p *Namespace) Authorizer() security.Authorizer
- func (p *Namespace) BoundNamesAcrossPhases() []string
- func (p *Namespace) BoundSymbolNames() values.Value
- func (p *Namespace) Compile() *EnvironmentFrame
- func (p *Namespace) ContractEnforcement() bool
- func (p *Namespace) DeleteExtensionState(key any)
- func (p *Namespace) EffectiveAuthorizer() security.Authorizer
- func (p *Namespace) EffectiveRegistry() any
- func (p *Namespace) EnvMap() map[string]string
- func (p *Namespace) EqualTo(v values.Value) bool
- func (p *Namespace) Expand() *EnvironmentFrame
- func (p *Namespace) ExportIndex() (any, bool)
- func (p *Namespace) ExtensionState(key any) (any, bool)
- func (p *Namespace) FileResolver() FileResolver
- func (p *Namespace) FormRegistry() any
- func (p *Namespace) IOState() any
- func (p *Namespace) ImmutableTopLevel() bool
- func (p *Namespace) InlineHOFTemplates() InlineHOFTemplateStore
- func (p *Namespace) InlineThreshold() (int, bool)
- func (p *Namespace) InternSyntax(k values.Value, v syntax.SyntaxValue) syntax.SyntaxValue
- func (p *Namespace) IsVoid() bool
- func (p *Namespace) LibraryEnvFactory() LibraryEnvFactory
- func (p *Namespace) LibraryRegistry() LibrarySearcher
- func (p *Namespace) LookupLibraryEnv(scope *syntax.Scope) *EnvironmentFrame
- func (p *Namespace) MaxExpandDepth() (int, bool)
- func (p *Namespace) ModuleInstance(path string) (*ModuleInstance, bool)
- func (p *Namespace) NewChildNamespace(opts ...NamespaceOption) *Namespace
- func (p *Namespace) NewChildRuntime() *EnvironmentFrame
- func (p *Namespace) NewSchemeReportNamespace() *Namespace
- func (p *Namespace) Phases() *PhaseRegistry
- func (p *Namespace) RegisterLibraryScope(scope *syntax.Scope, env *EnvironmentFrame)
- func (p *Namespace) Registry() any
- func (p *Namespace) Root() *Namespace
- func (p *Namespace) Runtime() *EnvironmentFrame
- func (p *Namespace) SchemeString() string
- func (p *Namespace) SetAuthorizer(auth security.Authorizer)
- func (p *Namespace) SetContractEnforcement(on bool)
- func (p *Namespace) SetEffectiveRegistry(reg any)
- func (p *Namespace) SetEnvMap(m map[string]string)
- func (p *Namespace) SetExportIndex(idx any)
- func (p *Namespace) SetExtensionState(key, value any)
- func (p *Namespace) SetFileResolver(resolver FileResolver)
- func (p *Namespace) SetFormRegistry(v any)
- func (p *Namespace) SetIOState(v any)
- func (p *Namespace) SetImmutableTopLevel(on bool)
- func (p *Namespace) SetInlineHOFTemplates(store InlineHOFTemplateStore)
- func (p *Namespace) SetInlineThreshold(n int)
- func (p *Namespace) SetLibraryEnvFactory(f LibraryEnvFactory)
- func (p *Namespace) SetLibraryRegistry(registry LibrarySearcher)
- func (p *Namespace) SetMaxExpandDepth(n int)
- func (p *Namespace) SetModuleInstance(path string, inst *ModuleInstance)
- func (p *Namespace) SetRegistry(reg any)
- func (p *Namespace) Store() *GlobalEnvironmentFrame
- func (p *Namespace) SyntaxInternCount() int
- type NamespaceOption
- type OriginRef
- type Phase
- type PhaseKey
- type PhaseRegistry
Constants ¶
const ( // BindingTypeUnknown is the zero value used for pre-allocated, not-yet- // bound slots in LocalEnvironmentFrame. It must not be observed by // callers outside the environment package. BindingTypeUnknown = BindingType(iota) // BindingTypeVariable indicates a regular variable binding (from define, let, lambda parameters). BindingTypeVariable // BindingTypeSyntax indicates a syntax transformer binding (from define-syntax). // These bindings live in the expand phase environment. BindingTypeSyntax // BindingTypePrimitive indicates a compile-time binding (special forms, auxiliary syntax). BindingTypePrimitive )
Variables ¶
This section is empty.
Functions ¶
func AmbientScopes ¶ added in v1.19.0
AmbientScopes returns the ambient scope set: the empty, NON-NIL set that a reference written outside any macro expansion carries.
The read entry points no longer confuse the two: GetBinding and GetLocalIndex take a syntax.ScopeSet, which separates wildcard (ScopeSet.IsAll) from empty structurally, and syntax.ScopesOf(nil) is the empty set rather than a wildcard. (EnvironmentFrame.GetGlobalIndex takes no scope argument at all and is unconditionally a wildcard.) Binding CREATION is the one surviving nil-as-wildcard path (MaybeCreateLocalBinding dedups on `scopes == nil`), so a creation caller that means "ambient" must pass this set rather than nil.
Every reflective read of a bare symbol wants this, not a wildcard: a values.Symbol carries no scope set, so when several hygiene-distinct bindings share a name a wildcard resolves by slot order — an expansion-order artifact, not an answer to the caller's question.
func SameBinding ¶ added in v1.19.0
SameBinding reports whether two bindings denote the same variable for the purpose of identifier equality (free-identifier=? and ER-compare): the same binding object, or two bindings that share one provenance root — a library define and its imports, under any rename or re-export. Two distinct defines of one name have different roots and are NOT the same, so this deliberately does not collapse into "same value" the way pointer- or value-equality would. Bindings with no origin (program-top-level defines) match only as the identical object.
A library define carries its self-root from finalization (stampLibraryExport- Origins), so comparing a library-internal binding against an IMPORT of itself matches — which is what lets ER-compare, resolving renames at the definition site, adopt this (option B in plan 2026-07-24-free-identifier-origin-provenance- design).
Types ¶
type Binding ¶
type Binding struct {
// contains filtered or unexported fields
}
Binding represents a variable binding in the environment. It stores the bound value, the binding type (variable, syntax, or primitive), and an optional pointer to compile-time metadata (scopes, source location).
func NewBinding ¶
func NewBinding(value values.Value, bindingType BindingType) *Binding
NewBinding creates a new binding with the given value and type. The binding has no scopes (for backward compatibility with non-hygienic code).
func NewBindingWithScopes ¶
func NewBindingWithScopes(value values.Value, bindingType BindingType, scopes []*syntax.Scope) *Binding
NewBindingWithScopes creates a binding with associated scopes (for hygiene)
func NewBindingWithSource ¶
func NewBindingWithSource(value values.Value, bindingType BindingType, scopes []*syntax.Scope, source *syntax.SourceContext) *Binding
NewBindingWithSource creates a binding with source location information.
func (*Binding) BindingType ¶
func (p *Binding) BindingType() BindingType
BindingType returns the type of this binding (variable, syntax, or primitive).
func (*Binding) Doc ¶
Doc returns the documentation string for this binding. Returns empty string for bindings without documentation.
func (*Binding) InlineHOFName ¶ added in v1.19.0
InlineHOFName returns the canonical name of the curated HOF this binding is, or "" if it is not a stamped inline HOF. The inline dispatch selects the template by this identity rather than the call-site surface name, so an import-renamed curated HOF inlines its OWN template.
func (*Binding) InlineHOFParam ¶
InlineHOFParam reports the callback parameter index of a curated inline-HOF binding (callback specialization Strategy A), or -1 when this binding is not a curated inline HOF. The gating BindingMeta.InlineHOF flag makes -1 the correct answer for an unstamped binding and for a binding whose meta exists but carries no inline-HOF stamp (the UpdateMeta zero value). Read by the compiler's inline-HOF dispatch to decide whether to attempt call-site specialization.
func (*Binding) IsCaptureSafe ¶
IsCaptureSafe reports whether this binding's callee cannot invoke a Scheme procedure — a Go primitive stamped from !PrimitiveSpec.InvokesProcedure at registration, or a Scheme procedure proven capture-safe at compile time (ProcedureBodyIsCaptureSafe). The frame-reclaim classifier pairs it with IsStable() — capture-safe AND non-rebindable — to trust a callee. Returns false when no metadata is set: the conservative default, an unstamped binding is never trusted. Unlike IsStable (which ORs in Imported), this reads CaptureSafe alone: Imported does NOT imply capture-safe (see the BindingMeta.CaptureSafe invariant).
func (*Binding) IsImported ¶
IsImported returns whether this binding was imported from a library.
func (*Binding) IsStable ¶
IsStable reports the rebind-stability conclusion: the binding will not be rebound. Imported is standing evidence for that conclusion (R7RS forbids set! on imports); Stable carries it when a proof discharges it by other means. This is NOT a set!-permission — that is IsImported alone (R7RS §5.2). Read by the frame-reclaim classifier (validate.classifyCallee). Renamed from the retired IsConstant, which falsely asserted "value known at compile time".
func (*Binding) Meta ¶
func (p *Binding) Meta() *BindingMeta
Meta returns the current BindingMeta snapshot, or nil if no metadata has been attached. For a global binding (cell != nil) the snapshot is immutable — do not write through it; use UpdateMeta. Callers that read metadata fields should nil-check the returned pointer; the convenience getters (Scopes, Source, Doc, IsImported, IsStable) wrap this pattern.
func (*Binding) Origin ¶ added in v1.19.0
Origin returns the import-provenance root of this binding, or nil if it is a plain (non-imported) define, which is its own root.
func (*Binding) Scopes ¶
Scopes returns the hygiene scopes associated with this binding. Returns nil for bindings without hygiene information.
func (*Binding) SetValue ¶
SetValue updates the value stored in this binding. Global bindings publish atomically (paired with the lock-free reader in Value); local bindings write the plain field.
func (*Binding) Source ¶
func (p *Binding) Source() *syntax.SourceContext
Source returns the source location where this binding was defined. Returns nil for bindings without source information.
func (*Binding) UpdateMeta ¶ added in v1.19.0
func (p *Binding) UpdateMeta(fn func(*BindingMeta) bool) bool
UpdateMeta mutates this binding's compile-time metadata and is the only metadata mutator API. fn receives a *BindingMeta to modify and returns whether it changed anything; UpdateMeta returns that same result. Adding a new metadata field thus requires editing only the BindingMeta struct itself; no parallel getter/setter accessor pair is needed. Usage:
b.UpdateMeta(func(m *BindingMeta) bool {
m.Imported = true
return true
})
For a global binding (cell != nil) the update is copy-on-write under an atomic CAS, so it is safe against concurrent compiles and lock-free readers, and fn may be re-run on CAS contention — fn MUST therefore depend only on the *BindingMeta it is handed, never on captured cross-call state (return the "did I change it?" answer instead of recording it in a closed-over variable). For a local binding (single-threaded) fn runs exactly once, in place.
type BindingID ¶
type BindingID struct {
Frame *LocalEnvironmentFrame
Slot int
}
BindingID is a stable identifier for a local binding, safe to use as a map key even when the backing []Binding slice is reallocated by append. The Frame pointer identifies the heap-allocated LocalEnvironmentFrame (stable across slice growth) and Slot is the index within that frame.
This is the local-binding analog of GlobalIndex: GlobalIndex uses (*GlobalEnvironmentFrame, *Symbol), BindingID uses (*LocalEnvironmentFrame, int).
type BindingMeta ¶
type BindingMeta struct {
Scopes []*syntax.Scope
Source *syntax.SourceContext
Doc string
// Stable is the conclusion of a rebind-stability proof (this binding will
// not be rebound), never a synonym for the evidence. Imported is standing
// evidence, since R7RS forbids set! on imports, and IsStable() ORs it in;
// this flag carries the conclusion when a proof discharges it otherwise
// (defined-once ∧ ¬set! ∧ unit-closed for a top-level define).
//
// Two writers set it, both only under WithImmutableTopLevel (the default),
// which makes unit-closure hold by enforcement rather than inference (set!
// gate + redefine guard in compile_validated.go): the compiler, for a
// top-level define, from the validator's in-unit evidence (StableInUnit);
// and registry.WithStableBasePrimitives, for the ambient core primitives
// (+, car, <, …) at registration (registry/apply.go). Under
// WithMutableTopLevel it stays false for non-imported bindings.
//
// Read by the frame-reclaim classifier (validate.classifyCallee). The
// redefine guard treats a Stable ambient primitive as frozen, stricter than
// Imported, which a top-level define may still supersede per R7RS §5.3.1.
Imported, Stable bool
// CaptureSafe marks a binding whose callee cannot invoke a Scheme procedure and
// therefore cannot transitively capture a continuation. Two writers stamp it: a
// Go primitive at registration from !PrimitiveSpec.InvokesProcedure
// (registry/apply.go), and a Scheme procedure proven capture-safe at compile
// time (compile_define.go via validate.ProcedureBodyIsCaptureSafe). It is a
// field because pkg/internal/validate cannot import pkg/registry, the same
// boundary Stable crosses. The frame-reclaim classifier trusts a callee only
// when CaptureSafe AND Stable both hold: "cannot capture", plus "cannot be
// rebound to something that can".
//
// INVARIANT: do NOT fold a sibling flag into IsCaptureSafe() the way IsStable
// ORs in Imported. Imported does not imply capture-safe (an imported apply or
// map is Imported yet invokes a procedure). Likewise a user redefinition of a
// primitive name is stamped ONLY if its own body proves capture-safe, which is
// how the classifier avoids trusting a capturing shadow by name.
CaptureSafe bool
// InlineHOF marks a curated higher-order procedure whose single-sequence
// case-lambda clause may be inlined at a call site that independently proves
// the callback capture-safe (callback specialization Strategy A). Stamped,
// never auto-derived, on the sealed-base tail HOFs post-bootstrap (for-each,
// vector-map, vector-for-each, string-map, string-for-each) and on import-gated
// ones from their library (fold, srfi/1); the curated set lives in
// compilation.inlineHOFSpecs. Consumed by the compiler's inline-HOF dispatch.
//
// ORTHOGONAL to CaptureSafe: an inline HOF applies its callback, so it is
// itself NOT capture-safe (for-each.IsCaptureSafe() is false, pinned by
// capture_safety_test.go). This flag says "inlinable WHEN the callback is
// proven safe".
//
// A bool, not a -1-sentinel int, keeps the capability zero-value-correct: a
// sentinel would read 0 ("callback param 0") on every binding that calls
// UpdateMeta, i.e. every primitive (registry/apply.go), falsely marking them
// inline HOFs. With the bool, &BindingMeta{} means "not an inline HOF", so
// adding a metadata field still needs no constructor edits.
InlineHOF bool
// InlineHOFCallbackParam is the callback's parameter index, read ONLY when
// InlineHOF is true. Stored as data (not hardcoded 0) so a future HOF whose
// callback is not the first parameter is handled by the same path.
InlineHOFCallbackParam int
// InlineHOFName is the CANONICAL name of the curated HOF this binding is (the
// inlineHOFSpecs key: "fold", "map", …), recorded at stamp time and read ONLY
// when InlineHOF is true. The inline dispatch selects the template by THIS
// identity, never by the call-site surface name — otherwise a curated HOF
// imported-and-renamed onto another curated HOF's name (e.g. fold as fold-right)
// would inline the wrong template. Empty on the zero value / unstamped bindings.
InlineHOFName string
// Origin is the provenance root of this binding: the root (define ...) it
// ultimately came from (see OriginRef); nil for a program-top-level define,
// which has no library identity. free-identifier=? AND ER-compare read it via
// SameBinding — a library define and its imports share one root and denote the
// same variable, while two distinct defines of one name have different roots.
// Set at the library's finalization for a library define (stampLibraryExport-
// Origins) or propagated at import (markBindingImported); the nil zero value is
// meaningful, so like the sibling flags above it needs no constructor edit.
Origin *OriginRef
// Predeclared marks a slot minted by a letrec* PRE-SCAN whose define has not
// been compiled yet — a location that exists so a forward reference can
// resolve, carrying no claim about what the name DENOTES.
//
// It rides in the meta rather than in Binding.bindingType because bindingType
// is a plain field outside the atomicCell, read lock-free by cachedBindings
// and by the frame optimizer's IsStable/IsCaptureSafe; promoting a type in
// place would be an unsynchronized write to it. UpdateMeta's copy-on-write CAS
// is the existing race-safe channel, and both writers (predeclareBinding,
// compile_define) already call it for their other stamps.
//
// Set ONLY on the global arm of predeclareBinding (top-level and library
// bodies). A lambda/let body's predeclaration is local and keeps plain
// letrec* per R7RS §5.3.2, so it never carries this.
Predeclared bool
}
BindingMeta holds compile-time metadata (scopes, source location) never read during VM execution, but read and written concurrently across SRFI-18 threads at compile time (two threads compiling a "define" of the same top-level name). A global binding therefore publishes it copy-on-write through its atomicCell (see UpdateMeta), so a reader always sees a complete, immutable snapshot. Behind a pointer so runtime Binding copies (the hot path) move a pointer.
type BindingRef ¶
type BindingRef struct {
// contains filtered or unexported fields
}
BindingRef names any binding — a resolved local slot, or a symbolic global that need not exist yet. All fields are comparable, so BindingRef is usable as a Go map key. Exactly one arm is meaningful, selected by kind.
The global arm is deliberately symbolic (a Key string, not a resolved GlobalIndex): a top-level (set! …)/(define …) is named during validation, before the compiler creates the corresponding global binding, so no frame slot exists yet. This is the local-or-global identity used to track "was this binding mutated / is it an inline candidate" within one compilation unit.
BindingRef is NOT a substitute for GlobalIndex. GlobalIndex carries Env *GlobalEnvironmentFrame for cross-library macro hygiene (definition-site resolution); the symbolic global arm here drops that, so two hygiene-distinct top-level bindings of the same name share a ref. That over-match is safe for a conservative mutation/stability set (it can only forfeit an optimization, never wrongly apply one), but it means BindingRef must not be used where cross-library binding identity matters.
func GlobalRef ¶
func GlobalRef(key string) BindingRef
GlobalRef returns a BindingRef naming a global binding symbolically, by the symbol's Key. The named binding need not exist yet.
func LocalRef ¶
func LocalRef(bid BindingID) BindingRef
LocalRef returns a BindingRef naming a resolved local binding.
func (BindingRef) IsGlobal ¶
func (p BindingRef) IsGlobal() bool
IsGlobal reports whether the reference names a global binding.
func (BindingRef) IsLocal ¶
func (p BindingRef) IsLocal() bool
IsLocal reports whether the reference names a local binding.
func (BindingRef) IsValid ¶
func (p BindingRef) IsValid() bool
IsValid reports whether the reference names a binding (local or global).
type BindingRefKind ¶
type BindingRefKind uint8
BindingRefKind discriminates the two halves of the binding domain a BindingRef can name. The zero value is intentionally invalid so a zero BindingRef is never == a resolved one.
const ( // BindingRefInvalid is the zero value: not a reference to any binding. BindingRefInvalid BindingRefKind = iota // BindingRefLocal names a resolved local binding by frame + slot. BindingRefLocal // BindingRefGlobal names a global binding symbolically, by symbol Key. BindingRefGlobal )
type BindingType ¶
type BindingType int
BindingType represents the type of a binding in the environment.
Three of the four constants are observable:
- BindingTypeVariable — regular runtime variables.
- BindingTypeSyntax — syntax transformers.
- BindingTypePrimitive — compile-time bindings (special forms, etc).
BindingTypeUnknown (the zero value) is internal scaffolding only. It is the type of pre-allocated slots in NewLocalEnvironment before they are assigned a real binding type, and is never observed by GetBinding or any external consumer. It exists solely to be a meaningful zero value for the BindingType field of a freshly constructed Binding in a pre-allocated frame slot. Removing it would require a sentinel layer (e.g. nil-binding markers in LocalEnvironmentFrame.bindings) and is not justified by the current call-site set.
type EngineServices ¶
type EngineServices struct {
// contains filtered or unexported fields
}
EngineServices co-locates the engine-lifetime, layering-opaque services that were previously scattered as bare any fields on Namespace. Allocated once by the root Namespace and shared by pointer: every child copies the *EngineServices at construction (exactly as it already copies registry/authorizer), so the whole namespace tree reads and writes one struct — no root() walk, one sync.RWMutex. Adding the next such service is a field here plus its two Namespace accessors.
Concurrency layout invariant: read-mostly handles are grouped first; the exportIndex lazy-cache and its RWMutex are LAST. exportIndexMu's word is written on every RLock (concurrent (apropos)); keeping it off the cache line shared with the read-mostly handles prevents false sharing across SRFI-18 threads. New tenants join the read-mostly group, never after the mutex.
type EnvironmentFrame ¶
type EnvironmentFrame struct {
// contains filtered or unexported fields
}
EnvironmentFrame represents an environment frame in the hierarchy.
Type Relationships ¶
The environment system has four types with distinct responsibilities:
┌─────────────────────────────────────────────────────────────────────────┐
│ Namespace │
│ (Per-VM instance: owns syntax interning, phases, libraries) │
│ │
│ syntaxInterns ──── map[Value]SyntaxValue (thread-safe) │
│ phases ─────────── *PhaseRegistry │
│ libraryRegistry ── LibrarySearcher (*compilation.LibraryRegistry) │
│ runtime ────────── *EnvironmentFrame (phase 0) │
└─────────────────────────────────────────────────────────────────────────┘
│
│ owns
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ EnvironmentFrame │
│ (Lexical scope node: links local/global bindings, parent chain) │
│ │
│ parent ─────────── *EnvironmentFrame (lexical parent, nil at top) │
│ local ──────────── LocalEnvironmentFrame (value; keys==nil → none) │
│ global ─────────── *GlobalEnvironmentFrame (define bindings) │
│ phaseLevel ─────── Phase (RELATIVE level; 0 == the owner's runtime) │
│ phases ─────────── *PhaseRegistry (shared reference) │
│ namespace ───────── *Namespace (back-reference) │
└─────────────────────────────────────────────────────────────────────────┘
│ │
│ contains │ contains
▼ ▼
┌───────────────────────────┐ ┌────────────────────────────────────────┐
│ LocalEnvironmentFrame │ │ GlobalEnvironmentFrame │
│ (Single scope bindings) │ │ (The owner's whole binding store) │
│ │ │ │
│ keys ── map[Symbol][]int │ │ keys ────── map[Symbol][]slotRef │
│ bindings ── []Binding │ │ bindings ──── []*Binding │
└───────────────────────────┘ └────────────────────────────────────────┘
Ownership and Sharing ¶
- Namespace: Root owner. One per Wile VM instance.
- EnvironmentFrame: Many per VM. Share namespace and phases references.
- GlobalEnvironmentFrame: One per OWNER, not one per phase. Every phase view and every sealed-write view is a thin frame over the SAME store, differing only in the (phase, sealed) coordinates its reads probe and its writes stamp. No direct Namespace back-reference (reach Namespace via the owning frame).
- LocalEnvironmentFrame: One per lexical scope. No external references.
Lexical Hierarchy (parent chain) ¶
(lambda (x) ; EnvironmentFrame A: local={x}, parent=TopLevel
(let ((y 1)) ; EnvironmentFrame B: local={y}, parent=A
(lambda (z) ; EnvironmentFrame C: local={z}, parent=B
(+ x y z))))
Phase Hierarchy (via PhaseRegistry) ¶
Namespace
└── PhaseRegistry
├── [0] Runtime EnvironmentFrame (normal execution)
├── [1] Expand EnvironmentFrame (macro expansion, for-syntax)
├── [2] Compile EnvironmentFrame (syntax compilers, for-meta 2)
├── [3…] minted on demand as the macro tower climbs
└── [-1] Template EnvironmentFrame (for-template, future)
The indices are RELATIVE levels, counted from THIS owner's runtime — see the Phase type. The registry is per-owner, so a library env's [1] and the namespace's [1] are different frames, and the named levels are the ones the top level uses rather than the ones that exist.
These are VIEWS, not owners: every entry shares the one GlobalEnvironmentFrame and the one Namespace. Phase separation is key disjointness in that store — a phase-N read is a candidate only against slots at exactly phase N or the ambient coordinate — not a per-phase store and not a parent link.
Binding Lookup ¶
Two-phase search: first all locals up parent chain, then globals.
func NewEnvironmentFrameWithParent ¶
func NewEnvironmentFrameWithParent(local *LocalEnvironmentFrame, parent *EnvironmentFrame) *EnvironmentFrame
NewEnvironmentFrameWithParent creates a new environment frame with the given local environment frame and parent environment frame.
The global environment frame is inherited from the parent. This is used for creating child frames within a phase (e.g., lambda bodies, let-syntax). The phase level, registry, and namespace are inherited from the parent. sealed is NOT inherited — the new frame is always mutable (the zero value), because a lexical child (a lambda body) is never a registration target, only a sealed-write view is. Panics if parent is nil - use NewNamespaceFrame() instead.
func NewNamespaceFrame
deprecated
func NewNamespaceFrame() *EnvironmentFrame
NewNamespaceFrame creates a new root environment frame via NewNamespace.
Deprecated: Use NewNamespace().Runtime() instead for per-instance syntax interning. This function now internally uses NewNamespace() to provide proper isolation.
func (*EnvironmentFrame) AtPhase ¶
func (p *EnvironmentFrame) AtPhase(phase Phase) *EnvironmentFrame
AtPhase returns the environment for the given phase level, creating it if needed. The argument is a RELATIVE level in the OWNER's tower, counted from its own runtime (0), not an absolute compilation stage; any int8 is legal, including negatives (-1 is for-template) and levels above 2, which the macro tower mints on demand.
A caller moving ALONG the tower must derive the argument from the receiver — p.PhaseLevel()+1, or NextPhase(), which is that expression named. Passing a constant pins the target to one level and silently collapses the tower for every frame that is not already at level 0. The exception is PhaseCompile, which is a fixed registry coordinate rather than a rung (see the Phase type), and is correctly reached by constant.
A climb rooted at a SEALED-WRITE view stays sealed wherever the target phase has a sealed-write view of its own; every other receiver, and every phase without one, resolves through the shared PhaseRegistry. That is what routes a bootstrap-macro define-syntax (compiled with env == the sealed-write root, so NextPhase() lands here at phase 1) into the phase-1 sealed-write view, while user code (env == the ordinary root view) writes the mutable tier. Above phase 1 there is no sealed-write view, so a transformer body that defines a macro climbs off the sealed axis.
This is the primary method for cross-phase access with O(1) lookup time. The environment must have been created via NewNamespace().
func (*EnvironmentFrame) Compile ¶
func (p *EnvironmentFrame) Compile() *EnvironmentFrame
Compile returns the owner's PhaseCompile view, creating it if needed. This is where registry apply installs syntax compilers and auxiliary keywords, and where LookupSyntaxCompiler reads them back.
Unlike Expand, reaching this one by constant is correct: PhaseCompile is a fixed registry coordinate, not a rung of the tower a frame climbs (see the Phase type). That a transformer body at level 1 also climbs to level 2 is a shared number, not a shared meaning.
func (*EnvironmentFrame) DefineOwnGlobal ¶ added in v1.19.0
func (p *EnvironmentFrame) DefineOwnGlobal(key *values.Symbol, bt BindingType, scopes []*syntax.Scope, v values.Value) (*GlobalIndex, error)
DefineOwnGlobal creates (or reuses) the binding for key under scopes in this frame's own global environment, then writes v to that binding.
It exists because create and write disagree about what a nil scope set means: creation treats nil as the EXACT empty set (so a macro-introduced binder gets its own slot), while a GlobalIndex built from a bare symbol resolves MATCH ANY (the name's first live slot, whatever its hygiene). A caller that creates under scopes and then hand-builds a bare-symbol index therefore creates one binding and writes a different one as soon as any macro has introduced the same name — the host's value lands on the macro's variable and the host's own binding stays void.
Callers that create a global and immediately give it a value should use this rather than reaching for an index of their own, so the nil question cannot be asked wrongly at the call site.
The returned index is the create's own PIN, non-nil whenever err is nil. A caller that also wants to stamp the binding it just defined takes it from here rather than re-resolving the name: a second lookup asks the same question with a weaker predicate, and leaves a window in which another compiling thread can append or delete a slot of that name.
It REFUSES a rebind of a Stable binding, and that is what makes Stable true ¶
BindingMeta.Stable is a compiler PROOF that a binding will not be rebound, and two consumers act on it irreversibly: the OpSelfTailCall emit gate, and compile_call_arity.go's compile-time arity refusal — which produces no program at all, so no runtime re-check can rescue it. The proof was enforced only inside the compiler, and this function is the one choke point under all three doors around it: Engine.Define, Engine.RegisterPrimitive and namespace-define! (Scheme-reachable at runtime, because (interaction-environment) IS the engine's root namespace) all reached a Stable slot and rewrote it. Measured before this guard: (define (f n) 'OLD) then Engine.Define("f", 42) left f == 42 with every compiled pin still holding the old proof.
So Stable is stamped only where the writer set is closed, and this is the refusal that closes it here. Keyed on the Stable FIELD, not IsStable(), for the same reason compile_define.go's redefine guard is: IsStable() ORs in Imported, and superseding an import must stay legal (R7RS §5.3.1).
`created` is the whole test. A fresh binding cannot carry a proof, so a first define of any name is unaffected, as is every define at coordinates that already differ — which, since the import relocation, includes defining a name the embedder also imported.
Refusal, not panic: all three callers return an error to a caller who can act on it, and the shadowing route (a define at different coordinates) is a legitimate way to get the effect.
func (*EnvironmentFrame) DeleteOwnGlobal ¶ added in v1.20.0
DeleteOwnGlobal removes the binding of sym that a scoped read at THIS view's own coordinates resolves to, returning whether one was removed.
Coordinates rather than tier order, for the reason DeleteBindingAt gives: a ranked delete through the mutable runtime view would reach the sealed primitive whenever no user shadow existed.
func (*EnvironmentFrame) EnsureLocalBinding ¶
func (p *EnvironmentFrame) EnsureLocalBinding(key *values.Symbol, bt BindingType) (*LocalIndex, bool)
EnsureLocalBinding returns the local binding for the given key, creating it if it does not already exist. Returns (index, true) if a new binding was created, or (index, false) if the binding already existed. Returns (nil, false) if the receiver is nil or has no local environment.
func (*EnvironmentFrame) EqualTo ¶
func (p *EnvironmentFrame) EqualTo(value values.Value) bool
EqualTo implements values.Value. R7RS §6.12 specifies that environments compare by eq? (pointer identity), not by structural equality of their bindings — the prior structural implementation was a latent correctness trap that no caller actually exercised. Use pointer identity here.
func (*EnvironmentFrame) Expand ¶
func (p *EnvironmentFrame) Expand() *EnvironmentFrame
Expand returns the owner's level-1 environment, creating it if needed. This is where a TOP-LEVEL define-syntax stores its transformer.
It is not "the expand phase" of an arbitrary frame: a define-syntax inside a transformer body stores one level above THAT body, not at level 1. Climbing sites must use NextPhase(); this accessor is for code that has already established it is talking about the owner's first rung.
func (*EnvironmentFrame) FileResolver ¶
func (p *EnvironmentFrame) FileResolver() FileResolver
FileResolver returns the file resolver. Shortcut for p.Namespace().FileResolver(); see the comment block above.
func (*EnvironmentFrame) GetBinding ¶
GetBinding returns the binding for the given symbol that matches the provided query. It searches local bindings first (walking up the parent chain), then globals.
A wildcard query (AllScopes) means "match any" (no scope filtering). A specific or empty query enables hygienic resolution per Flatt's model with maximal binding selection (consistent with GetLocalIndex).
Panics with a wrapped werr.ErrAmbiguousBinding when two incomparable scope sets tie for the maximal match (Racket's "ambiguous binding"); the tie is refused, never broken by order.
func (*EnvironmentFrame) GetGlobalBinding ¶
func (p *EnvironmentFrame) GetGlobalBinding(key *GlobalIndex) *Binding
GetGlobalBinding returns the binding for the given GlobalIndex, searching global bindings in the current and parent environments. It returns nil if the binding does not exist. A deferred index (Env == nil) carries the reference's scope set, so this execution-time walk resolves hygienically rather than by bare name.
func (*EnvironmentFrame) GetGlobalIndex ¶
func (p *EnvironmentFrame) GetGlobalIndex(key *values.Symbol) *GlobalIndex
GetGlobalIndex returns the GlobalIndex of the binding for the given symbol, searching global bindings in the current and parent environments. It returns nil if the binding does not exist.
The returned GlobalIndex records the specific global frame where the binding was found, enabling cross-library macro hygiene (see GlobalIndex.Env).
This is the WILDCARD form — see GlobalEnvironmentFrame.GetGlobalIndex. Compiler callers want GetGlobalIndexWithScopes.
func (*EnvironmentFrame) GetGlobalIndexAcrossPhases ¶
func (p *EnvironmentFrame) GetGlobalIndexAcrossPhases(key *values.Symbol, scopes []*syntax.Scope) *GlobalIndex
GetGlobalIndexAcrossPhases searches for a global binding across phases (runtime → expand → compile) using read-only phase access. Returns the first GlobalIndex found, or nil if not found in any phase.
This is used during macro compilation to resolve free identifiers that may be defined in any phase (e.g., define in runtime, define-syntax in expand).
scopes is the REFERENCE's scope set and each phase is searched hygienically (maximal subset match), not by bare name. Nil means the empty set, not "any" — the same convention as GetGlobalIndexWithScopes. Bare-name search was correct only while a name owned one slot per frame: once a macro-generating macro is expanded twice, each expansion's phase-0 binder carries its own intro scope, so the name owns two slots and a wildcard walk hands BOTH generated inner macros whichever slot was created first. See the two-expansion jabberwocky case in pkg/wile/toplevel_binder_scope_test.go, which the single-expansion case below cannot detect.
The phase-0 (runtime) search reaching the mutable runtime frame's OWN defines is DELIBERATE and load-bearing — it is NOT the accidental cross-level leak that phase-frame parenting once allowed (closed when createPhaseEnv stopped reparenting and hermeticity became key disjointness in the store), and must NOT be routed to the phase-0 seal. A macro-generating-macro introduces a phase-0 define that a generated inner macro references by scope-aware identifier; only searching the runtime frame resolves that intro-scoped binding at compile time. Sealing it breaks R7RS §4.3 referential transparency — concretely, the jabberwocky/march-hare case in integration/testdata/r7rs-tests.scm:
(define-syntax jabberwocky
(syntax-rules ()
((_ hatter)
(begin (define march-hare 42)
(define-syntax hatter (syntax-rules () ((_) march-hare)))))))
(jabberwocky mad-hatter) (mad-hatter) ; => 42; sealing gives "no such binding march-hare"
(Verified 2026-07-10: hermeticizing the phase-0 search passes the compilation/machine/wile suites but fails the integration R7RS conformance suite here. Investigated as a possible "second hermeticity hole"; it is not.)
func (*EnvironmentFrame) GetGlobalIndexFromLibraryScopes ¶
func (p *EnvironmentFrame) GetGlobalIndexFromLibraryScopes(key *values.Symbol, scopes []*syntax.Scope) *GlobalIndex
GetGlobalIndexFromLibraryScopes searches for a binding by checking each scope against the root Namespace's scope registry. For each scope that maps to a library env, it searches that library AT THE REFERRING PHASE — this frame's own phaseLevel — and at no other.
The phase comes off the receiver rather than a parameter because every frame inherits phaseLevel from its lexical parent (NewEnvironmentFrameWithParent), so a lambda body inside a begin-for-syntax already carries phase 1, just as the body frame does.
Searching every phase is what made a library body's phase separation vanish: phase-0 code resolved a phase-1 define and phase-1 code resolved a phase-0 one, where the top level refuses both — silently, producing a wrong value in the first case and a #!void from a predeclared-unwritten slot in the second. See memory/2026-08-04-library-phase-isolation-design.local.md §1.
Reaching the library's PRIMITIVES from phase 1 is not this search's job — it falls out of GetGlobalIndexWithScopes's own reach, not a mechanism this function adds. A library env (NewChildRuntime) shares its store across every phase view, and GetGlobalIndexWithScopes's ranked probe (resolveGlobal → resolveRankedLocked) already includes tiers T2/T3 (sealed, at the referring phase and ambient) at every call — there is no parent walk post-fold, one store answers for all of a library's phase views. Before the store fold this same reach came from the phase frame's PARENT LINK to the library's own sealed base; the fold changed the mechanism (link → tier) without changing which primitives a phase-1 body can see. Before library envs owned their own sealed axis at all, this arm's phase-0 reach was the ONLY such route, which is why narrowing it alone would have stranded every begin-for-syntax body in every library (design E4).
The other GetGlobalIndexAcrossPhases caller (compile_syntax_rules.go) is untouched: it carries the R7RS §4.3 free-template-identifier carve-out, and the jabberwocky/march-hare case reaches it through that caller, not this one.
func (*EnvironmentFrame) GetGlobalIndexWithScopes ¶ added in v1.19.0
func (p *EnvironmentFrame) GetGlobalIndexWithScopes(key *values.Symbol, q syntax.ScopeSet) *GlobalIndex
GetGlobalIndexWithScopes is GetGlobalIndex with hygienic resolution: the binding whose scope set maximally matches the query wins. The empty query (EmptyScopes) resolves under the empty scope set, not "any" — pass AllScopes for wildcard resolution.
func (*EnvironmentFrame) GetLocalBinding ¶
func (p *EnvironmentFrame) GetLocalBinding(li *LocalIndex) *Binding
GetLocalBinding returns the binding for the given LocalIndex. It returns nil if the binding does not exist.
func (*EnvironmentFrame) GetLocalBindingByIndex ¶
func (p *EnvironmentFrame) GetLocalBindingByIndex(i int) *Binding
GetLocalBindingByIndex returns the local binding at the given index in the current local environment. It does not search parent environments. It panics if i is out of range for this frame's local bindings; callers must have obtained i from this frame.
func (*EnvironmentFrame) GetLocalBindingBySlotDepth ¶
func (p *EnvironmentFrame) GetLocalBindingBySlotDepth(slot, depth int) *Binding
GetLocalBindingBySlotDepth returns the binding at the given slot and depth without requiring a *LocalIndex allocation. This is the hot-path variant used by the VM's OpLoadLocal dispatch.
func (*EnvironmentFrame) GetLocalIndex ¶
func (p *EnvironmentFrame) GetLocalIndex(key *values.Symbol, q syntax.ScopeSet) *LocalIndex
GetLocalIndex returns the LocalIndex of the binding for the given symbol that matches the given query. A wildcard query (AllScopes) means "match any".
For a specific or empty query, this implements Flatt's "maximal" binding resolution: among all bindings whose scopes are a subset of the reference's scopes, the one with the LARGEST scope set is returned.
Returns nil if no matching local binding exists.
Panics with a wrapped werr.ErrAmbiguousBinding when two incomparable scope sets tie for the maximal match (Racket's "ambiguous binding"); the tie is refused, never broken by order.
func (*EnvironmentFrame) GlobalEnvironment ¶
func (p *EnvironmentFrame) GlobalEnvironment() *GlobalEnvironmentFrame
GlobalEnvironment returns the global environment frame.
func (*EnvironmentFrame) HasLocalVariableBinding ¶
HasLocalVariableBinding reports whether sym has a local variable binding satisfying the scope-set query q. Its consumer is the macro expander, deciding whether a local variable shadows a macro.
THE VALIDATOR NO LONGER CALLS IT. "Does this head denote a special form" is decided by binding IDENTITY in validate.headDenotesSpecialForm — resolve by scopes, refuse a non-variable, compare against the sealed binding — because a local-only test could not see a top-level define or a library import shadowing a form. Locality turned out to be the wrong question in the other direction too: an internal define's name is fabricated into a local frame even when it is an ordinary ∅-scoped top-level define, so the validator asks whether the BINDER CARRIES SCOPES instead.
The check implements Flatt's hygiene rule: a binding matches a reference when bindingScopes ⊆ useScopes. Bindings with no scopes (user code) match any use. A wildcard query (syntax.AllScopes) matches any binding of the name; pass syntax.ScopesOf(ref.Scopes()) for a hygienic reference-scoped check. Only BindingTypeVariable bindings are considered; syntax/primitive bindings do not shadow.
func (*EnvironmentFrame) InitApplyFrame ¶
func (p *EnvironmentFrame) InitApplyFrame(dst *EnvironmentFrame) int
InitApplyFrame populates dst from p's closure environment without allocating a new EnvironmentFrame. The caller is responsible for providing dst (e.g. from a pool). This is the pooling-friendly counterpart of NewApplyFrame.
func (*EnvironmentFrame) InitApplyFrameWithParent ¶ added in v1.20.0
func (p *EnvironmentFrame) InitApplyFrameWithParent(dst *EnvironmentFrame, parent *EnvironmentFrame) int
InitApplyFrameWithParent is InitApplyFrame with the runtime parent supplied separately, for callers that hold the parameter SHAPE and the parent as two values rather than as one materialized frame. A closure built by OpMakeClosure is exactly that: p is the lambda's compile-time frame (a template constant shared by every evaluation) and parent is the runtime environment captured at closure creation. Splitting them here is what lets MachineClosure skip materializing an intermediate frame per closure.
Only p.local is read off p. Everything else (global, phase, namespace) comes from parent, which is why substituting a different parent is sound.
Returns how many binding slots were actually transferred, which is p's copyCount and not dst's width — the difference is the merged `let` slots copyForApplyInto reslices past. The VM's BindingsCopied counter takes it from here rather than measuring len(dst.Bindings()) so that the meter keeps naming work done rather than frame width; it is already in a register at the copy, so returning it costs the hot path nothing.
func (*EnvironmentFrame) IsNamespaceRuntime ¶ added in v1.20.0
func (p *EnvironmentFrame) IsNamespaceRuntime() bool
IsNamespaceRuntime reports whether this frame is its namespace's own runtime frame — the engine root or a profile child, as opposed to a library env's runtime frame from NewChildRuntime (which shares the root namespace) or any inner lexical frame.
Use this only for that narrow question: pkg/wile asks it when deciding whether to re-register docs per import, and widening it there would re-run ApplyDocs on every import — wasted work, and a data race under concurrent SRFI-18 imports. The immutable-top-level define gate wants IsOwnerRoot instead, which also recognizes the sealed-write root.
func (*EnvironmentFrame) IsOwnerRoot ¶ added in v1.20.0
func (p *EnvironmentFrame) IsOwnerRoot() bool
IsOwnerRoot reports whether this frame is one of its NAMESPACE's own root views — the mutable root or the sealed-write root.
This is the honest form of the old (ns.Runtime() == p.env || ns.SealedBase() == p.env) comparison: the immutable-top-level define gate must fire for user top-level compilation AND for bootstrap compilation through the sealed-write view (that path is what stamps Stable onto bootstrap procedures — the optimizer's anchors), and must NOT fire for a library env's root views, which share the namespace pointer but are different frames (a library body keeps cross-form define/set! mutable, R2).
func (*EnvironmentFrame) IsTopLevel ¶
func (p *EnvironmentFrame) IsTopLevel() bool
IsTopLevel returns true if this is the top-level environment frame (no parent).
func (*EnvironmentFrame) IsVoid ¶
func (p *EnvironmentFrame) IsVoid() bool
IsVoid reports whether this environment frame pointer is nil. Required by values.Value (see SchemeString comment).
func (*EnvironmentFrame) LibraryRegistry ¶
func (p *EnvironmentFrame) LibraryRegistry() LibrarySearcher
LibraryRegistry returns the library registry. Shortcut for p.Namespace().LibraryRegistry(); see the comment block above. Callers needing the full *compilation.LibraryRegistry can type-assert.
func (*EnvironmentFrame) LocalBindingsSlice ¶
func (p *EnvironmentFrame) LocalBindingsSlice() []Binding
LocalBindingsSlice returns the raw local bindings slice, bypassing the nil-keys check in LocalEnvironment(). This exposes the pre-allocated capacity that pooled frames retain across reset cycles.
func (*EnvironmentFrame) LocalEnvironment ¶
func (p *EnvironmentFrame) LocalEnvironment() *LocalEnvironmentFrame
LocalEnvironment returns the local environment frame, or nil if none.
func (*EnvironmentFrame) MaybeCreateLocalBinding ¶
func (p *EnvironmentFrame) MaybeCreateLocalBinding( key *values.Symbol, bt BindingType, scopes []*syntax.Scope, source *syntax.SourceContext, ) (*LocalIndex, bool)
MaybeCreateLocalBinding creates a local binding with scope-aware deduplication. A slot is reused only by a binder carrying the SAME scope set; any other scope set, even a compatible one, is a different variable and gets its own slot (see scopeSetsEqual).
Nil scopes means "match any" during dedup (pre-hygiene callers). Returns (index, true) if created, (index, false) if already existed.
func (*EnvironmentFrame) MaybeCreateOwnGlobalBinding ¶
func (p *EnvironmentFrame) MaybeCreateOwnGlobalBinding(key *values.Symbol, bt BindingType, scopes []*syntax.Scope) (*GlobalIndex, bool)
MaybeCreateOwnGlobalBinding creates a new global binding in the owner store at THIS view's coordinates if it does not already exist there. It returns the GlobalIndex of the binding and a boolean indicating whether the binding was created (true) or already existed (false).
scopes become part of the binding's identity; a nil set is the ordinary user-written top-level define. Coordinates are the other half of that identity (see CreateGlobalBindingAt): a phase-0 define of a sealed name is a new slot, and so is a top-level define-for-syntax over the expand-phase registry copy — the copy sits at (1, sealed) and the define writes at (1, mutable), so the phase-1 case shadows for exactly the reason the phase-0 one does.
func (*EnvironmentFrame) MutableRuntime ¶
func (p *EnvironmentFrame) MutableRuntime() *EnvironmentFrame
MutableRuntime returns the per-Engine MUTABLE runtime view of this frame's namespace — the user top level where user defines land and where eval/load and SRFI-18 threads store top-level state. Its writes land in the mutable tier at phase 0; a read through it still reaches sealed primitives, by tier order.
Use this, NOT TopLevel(), when a primitive needs the frame for user-visible top-level mutations: TopLevel() answers "whichever view this lexical chain hangs from", which under a phase-1 chain or a bootstrap closure is not the user global at all, so storing a user define or thread state through it would miss. This names the recurring intent that was previously spelled `.Namespace().Runtime()` at every call site. (It resolves the namespace's runtime, which for a flat library frame is the engine's mutable global rather than the library's own transient frame — unlike the receiver-relative Runtime().)
func (*EnvironmentFrame) MutableRuntimeOrNil ¶
func (p *EnvironmentFrame) MutableRuntimeOrNil() *EnvironmentFrame
MutableRuntimeOrNil resolves the namespace's mutable runtime by walking the lexical parent chain, returning nil if no frame in the chain carries a namespace (rather than panicking like MutableRuntime). Some transient execution frames — a procedure body frame entered while running a call-with-values producer, say — are detached (nil parent, nil namespace); their owning namespace is only reachable via the MachineContext's parentMC, not the lexical chain. NewSubContext uses this to fall back to the parent context when the local env cannot resolve a namespace.
func (*EnvironmentFrame) Namespace ¶
func (p *EnvironmentFrame) Namespace() *Namespace
Namespace returns the Namespace for this frame.
func (*EnvironmentFrame) NewApplyFrame ¶
func (p *EnvironmentFrame) NewApplyFrame() *EnvironmentFrame
NewApplyFrame creates a new EnvironmentFrame for a closure application, fusing CopyForApply + NewEnvironmentFrameWithParent into one allocation. The source frame's local bindings are copied into the new frame, and the parent chain is set from the source's parent. It is the allocating counterpart of InitApplyFrame (the pooling-friendly form); both share the same parent-copy logic.
func (*EnvironmentFrame) NextPhase ¶ added in v1.19.0
func (p *EnvironmentFrame) NextPhase() *EnvironmentFrame
NextPhase returns the sibling frame one phase up from this frame's own level. Climbing the macro tower: a transformer body compiled against this frame expands as phase (phaseLevel+1) code, so define-syntax storage and macro lookup relative to it climb rather than collapsing into the single expand phase. At phaseLevel 0 this equals Expand(), so top-level behavior is unchanged (level-0 identity). Panics (wrapped) only on the impossible int8 overflow, which NextPhaseChecked rejects.
func (*EnvironmentFrame) NextPhaseChecked ¶ added in v1.19.0
func (p *EnvironmentFrame) NextPhaseChecked(base Phase) (*EnvironmentFrame, error)
NextPhaseChecked returns the sibling frame one phase up from base. The climb is computed in int and rejected if it leaves the int8 phase range, so a runaway self-referential macro hits a wrapped error instead of overflowing int8 (127+1 -> -128). base is explicit (not p.phaseLevel) so the ceiling is testable without constructing a phase-127 frame.
func (*EnvironmentFrame) OwnGlobalIndex ¶ added in v1.20.0
func (p *EnvironmentFrame) OwnGlobalIndex(key *values.Symbol, q syntax.ScopeSet) *GlobalIndex
OwnGlobalIndex returns a PINNED GlobalIndex for the binding of key that resolves under q at THIS view's own write coordinates, or nil if there is none.
This is the write path's re-resolve — "the binding I just created here" — and it is deliberately NOT the ranked read. Over one merged store a scoped read of a name that exists both sealed and mutable is answered by tier order; a coordinate-blind scoped lookup instead takes the first-seen of two equally unscoped candidates, so a top-level (define car …) would stamp the SEALED car's metadata and then refuse itself as a redefine of a Stable anchor.
func (*EnvironmentFrame) Parent ¶
func (p *EnvironmentFrame) Parent() *EnvironmentFrame
Parent returns the parent environment frame.
func (*EnvironmentFrame) PhaseLevel ¶
func (p *EnvironmentFrame) PhaseLevel() Phase
PhaseLevel returns this frame's level on its owner's macro tower, relative to that owner's runtime (0).
Comparing it against PhaseRuntime is well defined — it asks "is this the owner's own runtime", which every owner answers for itself. Comparing two frames' levels across owners is not: the number says how far up each frame sits in its own tower, not which stage of one compilation both are in. It is a base to compute from (import composes it with a shift; NextPhase adds one), not a stage tag.
func (*EnvironmentFrame) PreAllocateBindings ¶
func (p *EnvironmentFrame) PreAllocateBindings(n int)
PreAllocateBindings sets the local bindings slice to a zero-length slice with the given capacity. Used by the env frame pool to ensure fresh frames have sufficient capacity for copyForApplyInto to reslice instead of allocate. Must only be called on freshly constructed frames (before any other use). n must be non-negative; negative values are clamped to 0.
The capacity is void-filled, for the reason ResetForPool gives: a frame that has never been recycled still gets resliced onto, and copyForApplyInto does not write the merged-`let` tail.
func (*EnvironmentFrame) PresentPhases ¶ added in v1.20.0
func (p *EnvironmentFrame) PresentPhases() []Phase
func (*EnvironmentFrame) ResetForPool ¶
func (p *EnvironmentFrame) ResetForPool()
ResetForPool clears the EnvironmentFrame for return to the FreeList while preserving the local bindings backing array capacity. This mirrors the Stack pool pattern: clear full capacity (so GC can collect referenced values), zero the struct, then restore the slice header with len=0.
After reset, the frame is a valid zero-value EnvironmentFrame whose local.bindings has cap > 0 but len == 0. The next copyForApplyInto call will reslice instead of allocating when cap >= n.
The fill is voidBinding, not the zero Binding, and that is a correctness requirement rather than a nicety: copyForApplyInto reslices past a shape's merged `let` slots without writing them, so whatever this leaves in the capacity IS what those slots hold on the next call. voidBinding drops every reference the same way zeroing does — values.Void is an empty struct — so the GC argument above is unaffected.
func (*EnvironmentFrame) ResolveBindingID ¶
func (p *EnvironmentFrame) ResolveBindingID(key *values.Symbol, q syntax.ScopeSet) (BindingID, bool)
ResolveBindingID looks up a local binding by symbol and scope-set query and returns a stable BindingID. Returns the zero BindingID and false if the symbol does not resolve to a local binding.
func (*EnvironmentFrame) ResolveBindingRef ¶
func (p *EnvironmentFrame) ResolveBindingRef(key *values.Symbol, q syntax.ScopeSet) BindingRef
ResolveBindingRef names the binding a symbol refers to: a local ref when the symbol resolves to a local binding, otherwise a symbolic global ref. It is total — always returns a valid BindingRef — because the global arm needs no existing binding (a top-level set!/define is named before the compiler creates its global). Unlike ResolveBindingID, callers do not branch on a found/not-found bool; "not local" is itself a nameable (global) outcome.
func (*EnvironmentFrame) Runtime ¶
func (p *EnvironmentFrame) Runtime() *EnvironmentFrame
Runtime returns the runtime phase environment (phase 0). This is the mutable user top level where normal bindings live: the owner's ROOT VIEW, whose writes land in the mutable tier at phase 0.
func (*EnvironmentFrame) SchemeString ¶
func (p *EnvironmentFrame) SchemeString() string
SchemeString returns a Scheme-level string for this environment frame. EnvironmentFrame reaches the value plumbing because closures capture environments and store them as template literals (see machine.NativeTemplate.MaybeAppendLiteral); this method exists to satisfy values.Value, not because environment frames are ever printed by Scheme programs.
func (*EnvironmentFrame) SealedWriteViewAt ¶ added in v1.20.0
func (p *EnvironmentFrame) SealedWriteViewAt(phase Phase) *EnvironmentFrame
SealedWriteViewAt returns the view sealed registrations write through at this phase: this owner's sealed-write view over its one store, whose writes land in the sealed tier. A phase with no such view (2 and up) falls back to the receiver's own view at that phase, which is what leaves a library's primitive expanders exactly where they were.
The receiver must OWN a sealed axis — be the phase-0 entry of its own phase registry. Any other frame gets its own view at that phase instead. The precondition is what makes this a registration API rather than a way to obtain a sealed writer: `phases` is inherited by every lexical child (NewEnvironmentFrameWithParent), so testing it alone would let a lambda body hand out the owner's sealed-write view and write past the mutable tier. Rank living on the view rather than in the topology removed the frame identity this guard once protected; it did not remove the capability it gates.
Restoring it costs no reach. A sealed-write view asking for a HIGHER phase falls through to unsealedTargetAt → AtPhase, whose climb from a sealed view stays sealed, so it lands on the same frame either way.
The fallback resolves through the PhaseRegistry for any phase but the receiver's own and must not be called while holding the registry's lock.
func (*EnvironmentFrame) SetDeferredGlobalValue ¶ added in v1.20.0
func (p *EnvironmentFrame) SetDeferredGlobalValue(gi *GlobalIndex, v values.Value) error
SetDeferredGlobalValue writes through a DEFERRED index (Env == nil): the compile-time-unbound define/set! fallback. It resolves in the MUTABLE tier at this frame's phase and nowhere else.
The restriction is G13 preserved, not new caution — but "before the fold this wrote through the executing frame's OWN global, which held exactly the mutable layer" is true only for USER code, not in general. A bootstrap closure's captured frame IS the sealed base (parent nil), and pre-fold that frame's OWN .global was the sealed base's OWN store — the sealed layer, not the mutable one — so a deferred write executing there would have landed sealed, the opposite of the claim. The merged store holds the sealed tiers a user-frame walk could never reach, and a ranked resolve here would let set! of an unshadowed primitive name mutate the sealed entry in place under WithMutableTopLevel — where nothing is Stable to refuse it — a behavior change and a P1 breach; restricting to the mutable tier closes that for BOTH frame kinds, which is why this is not narrowed to "user frames only". A PINNED index is a different question and keeps its reach (machine_context.go's other branch).
Whether the sealed-rank case this guards against has a live trigger at all is a separate question from whether the guard is correct: this round's review measurement (task-6 report, "Fix round 1") found no compiler path that constructs a deferred (Env == nil) GlobalIndex for a STORE instruction — emitDefineStore and CompileValidatedSetBang, the only two literal-producing sites for OpStoreGlobal, both resolve through OwnGlobalIndex / GetGlobalIndexWithScopes, which pin whenever they succeed and raise a compile error otherwise. That is evidence of no KNOWN live trigger, not a proof of unreachability (a hand-built literal, or a future compiler path, could still reach this branch), so the restriction stays as the fail-closed answer either way.
func (*EnvironmentFrame) SetFileResolver ¶
func (p *EnvironmentFrame) SetFileResolver(resolver FileResolver)
SetFileResolver sets the file resolver. Shortcut for p.Namespace().SetFileResolver(); see the comment block above. Panics if the frame has no namespace (configuration on an un-namespaced frame would be silently dropped — a programmer error).
func (*EnvironmentFrame) SetGlobalBindingByIndex ¶
func (p *EnvironmentFrame) SetGlobalBindingByIndex(i int, bd *Binding)
SetGlobalBindingByIndex sets the global binding at the given index in the current global environment. It does not search parent environments. Thread-safe: uses full Lock for write access.
func (*EnvironmentFrame) SetLibraryRegistry ¶
func (p *EnvironmentFrame) SetLibraryRegistry(registry LibrarySearcher)
SetLibraryRegistry sets the library registry. Shortcut for p.Namespace().SetLibraryRegistry(); see the comment block above. Panics if the frame has no namespace (see SetFileResolver).
func (*EnvironmentFrame) SetLocalValue ¶
func (p *EnvironmentFrame) SetLocalValue(li *LocalIndex, v values.Value) error
SetLocalValue sets the value of the binding for the given LocalIndex. It returns an error if the binding does not exist.
func (*EnvironmentFrame) SetLocalValueBySlotDepth ¶
func (p *EnvironmentFrame) SetLocalValueBySlotDepth(slot, depth int, v values.Value) error
SetLocalValueBySlotDepth sets the value of the binding at the given slot and depth without requiring a *LocalIndex allocation. This is the hot-path variant used by the VM's OpStoreLocal dispatch.
func (*EnvironmentFrame) SetOwnGlobalValue ¶
func (p *EnvironmentFrame) SetOwnGlobalValue(gi *GlobalIndex, v values.Value) error
SetOwnGlobalValue sets the value of the binding for the given GlobalIndex. It returns an error if the binding does not exist.
A PINNED index carries its own coordinates and goes straight to the store — what a create hands back, and what DefineOwnGlobal writes through. A DEFERRED one (Env == nil — the compile-time-unbound define/set! fallback, and the VM's own OpStoreGlobal path) carries a symbol and a hygiene key and nothing else, so THIS VIEW supplies the coordinates. The store cannot do that for itself: it holds every phase and every rank at once and has no view to ask. Before the coordinates existed this fell through to a name lookup that took the first live slot of the name at ANY coordinates — for a bare (set! car …) shape, the sealed primitive.
This is not SetDeferredGlobalValue, whose reach is narrower on purpose (mutable tier only, G13): that one serves the VM's deferred OpStoreGlobal, where the executing frame is whatever a closure captured. Here the caller chose the view.
func (*EnvironmentFrame) TopLevel ¶
func (p *EnvironmentFrame) TopLevel() *EnvironmentFrame
TopLevel returns the frame the lexical parent chain terminates at: the PHASE VIEW this frame's scope chain hangs from. Views have no parent — the store fold removed the layer edges the chain used to climb — so this is a purely structural "which top level am I under" walk.
It answers "the owner's view at MY phase", not "where user defines land": under a phase-1 lexical chain it is the phase-1 view, and under a bootstrap closure it is the sealed-write root. Use MutableRuntime() when the question is the namespace's user global.
func (*EnvironmentFrame) WritesOwnerRootCoordinates ¶ added in v1.20.0
func (p *EnvironmentFrame) WritesOwnerRootCoordinates() bool
WritesOwnerRootCoordinates reports whether a global define through THIS view lands on the same slot an owner-root define would.
It is IsOwnerRoot's question asked the way the WRITE asks it. IsOwnerRoot is a frame-IDENTITY test, while the write is a COORDINATE operation (writeCoordinates), and the two diverge on a lexical child: a lambda body or a compile sub-frame inherits global/phase/namespace from its parent but is not the root object, so it writes the root's slot while failing the root's identity test. That divergence is the whole of finding 34a — compiling (define (f n) 'NEW) into a thunk and invoking it silently REBOUND an already- stable top-level f, where the same program through eval or load correctly refused.
Two conjuncts, and both are load-bearing:
- the same STORE. A library body compiles against NewChildRuntime, which SHARES the namespace (so the namespace pointer proves nothing) but mints its own GlobalEnvironmentFrame. Coordinates alone would therefore match the engine root's and arm the immutability gate inside every library body, breaking R2 (a library keeps its cross-form define/set! mutable).
- the same COORDINATES. This is what a lexical child passes and what makes the predicate wider than IsOwnerRoot at all.
Note that the fix is deliberately NOT to change `sealed` on a lexical child. It is left false on purpose (see NewEnvironmentFrameWithParent), and that is exactly what makes writeCoordinates resolve to (ExactPhase(0), mutable) and the write land on the runtime root's slot. Setting it true would redirect the write to (ANY, sealed) — a strictly worse outcome, and it would break the write this guard is protecting.
type FileResolver ¶
type FileResolver interface {
// ResolveAndOpen finds a file by name and returns an open handle plus
// the resolved path (used for load-path-stack tracking and error messages).
ResolveAndOpen(ctx context.Context, path string) (fs.File, string, error)
}
FileResolver resolves and opens files for include/load operations. Implementations control where files are found: the OS filesystem, an embedded filesystem, or any other fs.FS.
The concrete implementations (OSFileResolver, FSFileResolver, EmbedFileResolver, ChainFileResolver) live in machine/compilation/resolver/, backed by sourceload.Finder for file search. This interface is defined here so environment/ can store it without creating a circular import.
type GlobalEnvironmentFrame ¶
type GlobalEnvironmentFrame struct {
// contains filtered or unexported fields
}
GlobalEnvironmentFrame is one OWNER's whole binding store: every global binding a namespace or library env holds, at every phase, sealed and mutable alike, in one scope-keyed slot table.
Design: it has no hierarchy of its own, and after the store fold there is no hierarchy above it either — an owner's phase frames are VIEWS over this one instance, distinguished by the (phase, sealed) coordinates their writes stamp and the phase their reads probe at. What used to be a parent walk across (layer × phase) frames is resolveRankedLocked's tier order.
Note: syntax interning is delegated to Namespace via the owning EnvironmentFrame. (Symbols are not interned; eq? on symbols compares the .Key string.) GlobalEnvironmentFrame itself does not hold a back reference to its Namespace; ownership flows through EnvironmentFrame.
Thread safety: All access to keys and bindings is protected by mu. Fixes T2 from architectural review.
func NewGlobalEnvironmentFrame ¶
func NewGlobalEnvironmentFrame() *GlobalEnvironmentFrame
NewGlobalEnvironmentFrame creates a new, empty owner store.
func (*GlobalEnvironmentFrame) AmbientKeysAt ¶ added in v1.20.0
func (p *GlobalEnvironmentFrame) AmbientKeysAt(phase Phase) []values.Symbol
AmbientKeysAt returns the names holding a live binding under the ambient (empty) scope set AT phase: the names a reference written outside any macro expansion, in phase-N code, resolves.
Enumeration goes through the same ranked probe a single read makes, so the listing cannot drift from what the read finds. A raw range over p.keys would report every name in the store, including binders a macro template introduced (different variables that happen to share a name, reachable by no source-written reference) and entries at phases the caller cannot see — and enumerate-then-dereference then fails on exactly those names.
Order is unspecified: the result is built by ranging p.keys. Callers needing determinism must sort. (BoundSymbolNames, the only consumer, documents the same.)
Cost: O(names × slots-per-name) — a full ranked probe (resolveRankedLocked) per name — where the pre-fold AmbientKeys this generalizes ran a cheaper scope-only best-of per name (no tier walk, since a frame's own store held only one layer). Same asymptotic shape, larger constant per name. The only consumer, BoundSymbolNames, is a REPL-completion path, not a hot one; not restructured here. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) Bindings ¶
func (p *GlobalEnvironmentFrame) Bindings() []*Binding
Bindings returns a copy of the bindings slice. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) Copy ¶
func (p *GlobalEnvironmentFrame) Copy() *GlobalEnvironmentFrame
Copy creates a deep copy of the global environment frame. Bindings are batch-allocated (contiguous array) for cache locality and reduced GC pressure. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) CreateGlobalBindingAt ¶ added in v1.20.0
func (p *GlobalEnvironmentFrame) CreateGlobalBindingAt(key *values.Symbol, bt BindingType, scopes []*syntax.Scope, phase PhaseKey, sealed bool) (*GlobalIndex, bool)
CreateGlobalBindingAt creates a new global binding with the given key, type and resolution coordinates. Returns the GlobalIndex and whether a new binding was created (false if the binding already existed at those coordinates).
Reuse requires EXACT scope-set equality — see scopeSetsEqual for why compatibility (the subset predicate resolution uses) would be a hygiene hole here — AND coordinate equality: two entries of one name at different (phase, sealed) are different variables. That is what makes a phase-0 define a SHADOW of the sealed entry (new slot) rather than a write through it, and what makes a define-for-syntax over the registry's (1, sealed) expand copy a shadow for the same reason, at the phase above. Scope equality alone was sufficient only while coordinates were frame identity.
(ANY, mutable) is refused: no population produces it (design §4.1), and modeling it would give the wildcard a mutable row that outranked nothing.
The returned index is PINNED to the slot this call landed on, created or reused, carrying the creation scope set as its re-resolution query. Callers may write through it directly: it needs no paired re-resolve, and unlike a bare-name index it cannot drift onto a different slot of the same name. See the history note below for why it was deferred until 2026-08-06.
func (*GlobalEnvironmentFrame) DeleteBindingAt ¶ added in v1.20.0
func (p *GlobalEnvironmentFrame) DeleteBindingAt(sym *values.Symbol, scopes []*syntax.Scope, phase PhaseKey, sealed bool) bool
DeleteBindingAt removes the global binding for sym that resolves under the given scope set AT EXACTLY (phase, sealed). Returns true if one was found and removed.
Delete is a write, so it takes the writer's coordinates rather than the reader's tier order: over one merged store a ranked delete of `car` from the mutable runtime view would reach the SEALED primitive whenever no user shadow existed, which is precisely what namespace-undefine! refuses. Callers reach this through EnvironmentFrame.DeleteOwnGlobal, which derives the coordinates from the view.
Resolution goes through resolveAtCoordsLocked with a scoped (never wildcard) query — the literal call AmbientKeysAt and GetGlobalIndexWithScopes make — so delete cannot drift from the read surface at those coordinates. It removes exactly the binding a scoped read there would have returned, and deleting a name owned only by a macro-introduced binder is a no-op rather than the destruction of a binding the caller could not read.
A nil scopes argument means NONE — the empty scope set, same as AmbientScopes() — and never MATCH ANY. Nil is indistinguishable from an uninitialized value, so resolving it permissively fails open: a caller that merely forgot to thread its scopes would delete across a hygiene boundary with nothing in the signature to flag it. Delete therefore has no wildcard mode at all; "remove the name and every hygiene-distinct binding under it" is a legitimate but different operation, and nothing asks for it.
Note: the binding slot in p.bindings is not compacted — index-based references from compiled code would be stale. This is only safe for top-level REPL/eval bindings, not for bindings referenced by compiled bytecode.
It does not refuse a sealed coordinate, and nothing downstream depends on its declining to reach one. The write-side self-heal re-resolves at the PIN's own coordinates (healWriteLocked), so the worst a sealed delete can do is re-heal a sealed pin onto a sealed slot — the coordinate it already addressed. An earlier form of this note recorded the opposite, because SetOwnGlobalValue then re-resolved by name and leaned on an audited "no caller passes a sealed coordinate" premise to stay off the sealed tier. That filter is gone.
Thread-safe: uses full Lock for write access.
func (*GlobalEnvironmentFrame) GetOwnGlobalBinding ¶
func (p *GlobalEnvironmentFrame) GetOwnGlobalBinding(gi *GlobalIndex) *Binding
GetOwnGlobalBinding returns the binding for the given GlobalIndex from this store only. Unlike EnvironmentFrame.GetGlobalBinding it resolves nothing against the live environment: a PINNED index addresses its slot, and a stale pin re-heals through healReadLocked. A DEFERRED index (Env == nil) has no pinned slot and no coordinates, so it misses here — every production caller passes an index from OwnGlobalIndex or GetGlobalIndexWithScopes, and the VM routes a deferred one to EnvironmentFrame.GetGlobalBinding instead (machine_context.go, resolveGlobalBinding). Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) IsSealedBindingAt ¶ added in v1.20.0
func (p *GlobalEnvironmentFrame) IsSealedBindingAt(key *values.Symbol, q syntax.ScopeSet, phase Phase) bool
IsSealedBindingAt reports whether a read of key under q at phase resolves to a SEALED-tier slot — "the binding this name denotes here is part of the startup set", which is what refusing to undefine a primitive asks. False covers both "resolves to a mutable slot" and "resolves to nothing". Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) LiveSlots ¶ added in v1.20.0
func (p *GlobalEnvironmentFrame) LiveSlots() []NamedSlot
LiveSlots snapshots every live slot in the store: any phase, sealed or not. It is the "every binding this owner holds anywhere" enumeration that the doc/apropos walk wants, and it replaces the old union over every phase frame plus every sealed frame — which, now that all of those are views over one store, would range the same map once per view. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) SealedBindingAt ¶ added in v1.20.0
func (p *GlobalEnvironmentFrame) SealedBindingAt(key *values.Symbol, q syntax.ScopeSet, phase Phase) *Binding
SealedBindingAt returns the binding key resolves to under q at phase when the MUTABLE tier is skipped: what the startup set bound this name to, regardless of any user shadow. nil means NONE — no sealed-tier binding of that name is visible from phase.
It is the store form of the pre-fold "read the sealed base frame directly" fallback (setRecognizedPrimitive): with one merged store there is no narrower frame to address, so the narrowing is a tier floor on the probe. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) SealedGlobalIndexAt ¶ added in v1.20.0
func (p *GlobalEnvironmentFrame) SealedGlobalIndexAt(key *values.Symbol, q syntax.ScopeSet, phase Phase) *GlobalIndex
SealedGlobalIndexAt is SealedBindingAt's PIN: the same tier-floored probe, but returning the index of the slot it landed on rather than the binding there. nil means NONE.
The two answer different questions. A caller that only wants to read the startup set's value now takes the binding; a caller that wants to RECORD that resolution for a later compile — a synthesized reference pinned to its definition site, which is what the quasiquote expansion's list/cons/append heads need — takes the index, because a pin re-resolves inside its own query if its slot is later deleted while a bare *Binding cannot.
The query is recorded rather than widened to the wildcard: the sealed startup registrations are unscoped, so q is the empty set here, and re-resolution must stay inside that hygiene key rather than matching any scope set of the name. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) SealedSlots ¶ added in v1.20.0
func (p *GlobalEnvironmentFrame) SealedSlots() []NamedSlot
SealedSlots snapshots every live SEALED-tier slot in the store, at any phase. This is the sealed-filtered form of LiveSlots: the startup set a registry apply and the bootstrap load wrote, as distinct from anything user code has defined since. Thread-safe: uses RLock for read-only access.
func (*GlobalEnvironmentFrame) SetOwnGlobalValue ¶
func (p *GlobalEnvironmentFrame) SetOwnGlobalValue(gi *GlobalIndex, v values.Value) error
SetOwnGlobalValue sets the value of the binding for the given GlobalIndex. Returns an error if the binding does not exist.
It is the PINNED write. Every production caller passes an index carrying (Env, Slot) — a compile-time re-resolve at the writing view's own coordinates (OwnGlobalIndex), or the VM's pinned OpStoreGlobal branch. The deferred, name-resolved write is a different entry point with a different reach: EnvironmentFrame.SetDeferredGlobalValue, restricted to the mutable tier (G13).
The healWriteLocked fallback below is therefore NOT the non-pinned write path; it is the STALE-pin self-heal that pinnedSlotLocked documents — reached only when the pinned slot has been nil'd by a delete. It re-resolves at the pin's own coordinates, which is what keeps a re-heal from becoming an escalation: a pin emptied by namespace-undefine! can reach neither the SEALED slot of the same name underneath it nor that name's copy at another phase, both of which a coordinate-blind lookup could reach and no other write path can.
The error is ErrNoSuchBinding whether the name is absent entirely or merely absent at the pin's coordinates. Those are different facts; the message names the coordinates so the distinction survives to the reader.
Thread-safe: uses full Lock for write access.
type GlobalIndex ¶
type GlobalIndex struct {
Index *values.Symbol
Env *GlobalEnvironmentFrame
Slot int
// contains filtered or unexported fields
}
GlobalIndex identifies a global binding by its symbol key. Unlike LocalIndex which uses numeric indices, GlobalIndex uses the symbol directly since global bindings are accessed by name at runtime.
Env records the definition-site global frame for cross-library macro hygiene. When a macro references a non-exported helper from its defining library, Env ensures the VM resolves the binding in the library's environment rather than the use-site environment. Nil means "use the current environment" (backward compatible default). Slot addresses the binding within Env.bindings directly. It is meaningful ONLY when Env is non-nil: the two are set together by the frame that resolved the lookup, and a nil Env means no frame has been chosen yet, so the zero Slot is never consulted. This pairing is what lets a resolved global load index the bindings slice instead of re-hashing the symbol at every execution.
query is the hygiene key. For a deferred index (Env == nil) it is the reference's scope-set query, resolved against whatever environment is live when the instruction executes. For a PINNED index it is the query resolution matched on, kept so that re-resolution — which happens whenever the pinned slot no longer holds the binding, e.g. after a delete — stays inside the same hygiene boundary instead of falling back to bare name.
A wildcard query (AllScopes) re-resolves by bare name; a specific or empty query re-resolves under its scope set even when that set is empty, or a stale pinned index would silently cross a hygiene boundary after a delete-then-recreate: DeleteBindingAt nils the slots and drops the name, so once anything re-creates it a wildcard fallback would land on whatever binding now holds the name — including one whose scope set the reference could never reach. This one ScopeSet subsumes what a nil Scopes slice plus a scopeKeyed bool once encoded: a nil slice could not distinguish "matched the empty set" from "no key at all", and those demand opposite re-resolution.
phase and sealed are the pinned slot's RESOLUTION COORDINATES, set with Env and Slot and meaningful only alongside them. While the slot lives they are redundant — a slot is named by exactly one slotRef, so (Env, Slot) already determines them — which is why they take no part in EqualTo. They exist for the moment the slot STOPS living: a delete nils it, the pin falls through to re-resolution, and the query alone says which hygiene boundary to stay inside but nothing about which phase or tier. Without them re-resolution is phase-blind, and a pin addressing (0, mutable) re-heals onto any other slot of the same name — in the case that motivated recording them, the registry's phase-1 copy of a primitive, which sat at (1, mutable) then and is sealed now.
func NewGlobalIndex ¶
func NewGlobalIndex(key *values.Symbol) *GlobalIndex
NewGlobalIndex creates a new deferred GlobalIndex for the given symbol. Env is nil, so Slot is not meaningful; use newResolvedGlobalIndex when the owning frame and slot are known. Its query is the wildcard (AllScopes): a deferred bare-name index re-resolves by name.
func (*GlobalIndex) EqualTo ¶
func (p *GlobalIndex) EqualTo(value values.Value) bool
EqualTo returns true if this global index equals the given value.
Env participates in the comparison, by pointer. It is not provenance metadata: a non-nil Env is the binding store the VM reads and writes directly, with no parent walk (machine_context.go, OpLoadGlobal/OpStoreGlobal via GetOwnGlobalBinding and SetOwnGlobalValue). Two frames are two distinct `bindings` slices, so two GlobalIndex pinned to different frames denote different variables even when their symbol keys agree.
A nil Env is not "some frame we did not record" — it is a deferred lookup, resolved against whatever environment is live when the instruction executes. It is therefore never equal to a pinned index, even one whose frame today's walk would reach: the two are different operations, and a closure with a different env chain resolves them differently.
Slot participates whenever Env does. Once a frame keys its bindings by scope set, one symbol can name several distinct bindings in the same frame, so (Index, Env) no longer identifies a variable — the slot is what separates a macro-introduced binder from a user-written one of the same name.
func (*GlobalIndex) IsVoid ¶
func (p *GlobalIndex) IsVoid() bool
IsVoid returns true if this global index is nil.
func (*GlobalIndex) SchemeString ¶
func (p *GlobalIndex) SchemeString() string
SchemeString returns a string representation of this global index.
type InlineHOFTemplateStore ¶
InlineHOFTemplateStore returns a pre-validated inline-HOF loop template by HOF name (callback specialization Strategy A). The returned template is a *validate.ValidatedLambda, exposed here as any because environment/ is below validate/ in the import graph; the compilation consumer type-asserts. Mirrors LibrarySearcher: the minimum environment/ needs to hold a compilation artifact.
type LibraryEnvFactory ¶
type LibraryEnvFactory func(ctx context.Context, callerEnv *EnvironmentFrame, libraryName []string) (*EnvironmentFrame, error)
LibraryEnvFactory creates a fresh environment for an R7RS library. The returned environment must share the caller's Namespace for syntax interning, but have isolated bindings so library definitions don't leak.
The libraryName parameter contains the library name parts (e.g., ["scheme", "base"]) so the factory can implement per-library policies.
type LibrarySearcher ¶
type LibrarySearcher interface {
GetSearchPaths() []string
}
LibrarySearcher is implemented by library registries that support path-based file discovery. It is the minimum interface environment/ needs from a LibraryRegistry: the set of directories to search when resolving include and load paths.
The full *compilation.LibraryRegistry type implements this interface. Callers that need the full registry can type-assert from LibrarySearcher.
type LocalEnvironmentFrame ¶
type LocalEnvironmentFrame struct {
// contains filtered or unexported fields
}
LocalEnvironmentFrame stores local variable bindings for a single scope. It maps symbols to binding indices for efficient lookup. Local environments are created for lambda parameters and let-bound variables. Note: LocalEnvironmentFrame has no hierarchy of its own; the hierarchy is managed by EnvironmentFrame via its parent field.
func NewLocalEnvironment ¶
func NewLocalEnvironment(pcnt int) *LocalEnvironmentFrame
NewLocalEnvironment creates a new local environment frame with pre-allocated slots for the given parameter count. Each slot is initialized with a void binding of unknown type.
func (*LocalEnvironmentFrame) AppendAnonymousSlot ¶ added in v1.20.0
func (p *LocalEnvironmentFrame) AppendAnonymousSlot() int
AppendAnonymousSlot appends one binding slot that no symbol maps to, and returns its index.
It is the allocator for a compile-time frame whose slots are addressed positionally rather than by name: `let`-slot merging (compilation's shapeSlotFor) gives a merged `let` binder a slot in the enclosing lambda's frame while the NAME stays in the `let`'s own compile-time frame, where scope-set resolution can still shadow it. Adding the name here instead would alias a same-named parameter — MaybeCreateLocalBinding dedups on scope-set equality, and duplicate-parameter detection is that same call.
The slot is initialized to Void rather than the zero Binding, so a read that precedes any store sees `#!void` the way a freshly pushed frame's slot does. A letrec forward reference and OpBoxSlot both perform that read, so it is a requirement rather than tidiness.
copyCount is deliberately NOT raised: voidBinding is bit-for-bit what this appends, so an apply frame reaches the same state by not copying the slot at all. That exemption is the whole point of merging a `let` into the shape — the slots exist for free at compile time and cost nothing per call.
func (*LocalEnvironmentFrame) Bindings ¶
func (p *LocalEnvironmentFrame) Bindings() []Binding
Bindings returns the slice of bindings in this local environment.
func (*LocalEnvironmentFrame) CopyCount ¶ added in v1.20.0
func (p *LocalEnvironmentFrame) CopyCount() int
CopyCount returns how many of this frame's slots copyForApplyInto transfers. See the field comment: the difference from len(Bindings()) is the merged `let` slots, which cost nothing per call.
func (*LocalEnvironmentFrame) EnsureLocalBinding ¶
func (p *LocalEnvironmentFrame) EnsureLocalBinding(key *values.Symbol, bt BindingType) (*LocalIndex, bool)
EnsureLocalBinding returns the local binding for the given key, creating it if it does not already exist. Returns (index, true) if a new binding was created, or (index, false) if the binding already existed.
If the keys map is shared (from Copy), it is cloned before mutation (CoW). In practice, EnsureLocalBinding is only called during compilation, never at runtime, so the CoW path is a safety net rather than a hot path.
Note: With multi-slot keys, this returns slots[0] without scope discrimination. It is only valid for single-slot keys (fresh environments for lambda params, syntax-case pattern variables). Do not use on frames where MaybeCreateLocalBinding has created scope-distinct slots for the same key.
func (*LocalEnvironmentFrame) GetLocalBinding ¶
func (p *LocalEnvironmentFrame) GetLocalBinding(li *LocalIndex) *Binding
GetLocalBinding returns the binding at the given LocalIndex.
func (*LocalEnvironmentFrame) GetLocalIndex ¶
func (p *LocalEnvironmentFrame) GetLocalIndex(key *values.Symbol) *LocalIndex
GetLocalIndex returns the LocalIndex for the given symbol in this local environment. Returns the first slot for the key, or nil if not bound.
func (*LocalEnvironmentFrame) Keys ¶
func (p *LocalEnvironmentFrame) Keys() map[values.Symbol][]int
Keys returns a copy of the symbol-to-index mapping for this local environment. Each key maps to a slice of slot indices (common case: one element). Multiple slots per key occur when hygienic expansion creates same-name bindings with different scope sets in the same frame. The returned map is safe to mutate without affecting internal state.
func (*LocalEnvironmentFrame) MaybeCreateLocalBinding ¶
func (p *LocalEnvironmentFrame) MaybeCreateLocalBinding( key *values.Symbol, bt BindingType, scopes []*syntax.Scope, source *syntax.SourceContext, ) (*LocalIndex, bool)
MaybeCreateLocalBinding creates a local binding with scope-aware deduplication. A slot is reused only by a binder carrying the SAME scope set; any other scope set is a different variable and gets its own slot. Nil scopes means "match any".
Creation compares with scopeSetsEqual, not ScopesCompatible, for the reason spelled out at scopeSetsEqual (global_environment_frame.go): compatibility treats an empty binding scope set as matching anything, so a macro-introduced binder (scopes {m}) would reuse a scope-less binding of the same name instead of getting a slot of its own. Compatibility is the right predicate for LOOKUP, where a pre-hygiene binding is legitimately visible to every reference; it is the wrong one for deciding identity. This mirrors the global creation path rather than the local lookup path beside it.
A reused slot backfills Source but never Scopes. Under exact equality the slot already carries the scope set the caller asked for (or the caller passed nil and asked for nothing), so there is nothing to fill in; a Scopes write here could only overwrite an identity, which is the clobber the predicate above exists to prevent. Source is independent metadata and may legitimately be absent on an existing slot.
If the keys map is shared (from Copy), it is cloned before mutation (CoW). Cloning the map is not enough: the clone's slot slices alias the original's backing arrays, so slices.Clip forces the append to copy rather than extend one in place and publish a slot to the other frame.
func (*LocalEnvironmentFrame) SetLocalValue ¶
func (p *LocalEnvironmentFrame) SetLocalValue(li *LocalIndex, v values.Value) error
SetLocalValue sets the value of the binding at the given LocalIndex.
type LocalIndex ¶
type LocalIndex [2]int
LocalIndex represents the location of a local binding as [slot, depth]. The first element (slot/over) is the index within the local environment frame. The second element (depth/up) is how many parent frames to traverse. Example: [2, 1] means "binding at slot 2 in the parent frame".
func NewLocalIndex ¶
func NewLocalIndex(over, up int) *LocalIndex
NewLocalIndex creates a new LocalIndex with the given slot and depth.
func (*LocalIndex) EqualTo ¶
func (p *LocalIndex) EqualTo(i *LocalIndex) bool
EqualTo returns true if this index equals the given index.
func (*LocalIndex) GetBinding ¶
func (p *LocalIndex) GetBinding(env *EnvironmentFrame) *Binding
GetBinding retrieves the binding at this index from the given environment.
func (*LocalIndex) Over ¶
func (p *LocalIndex) Over() int
Over returns the slot index within the local environment frame.
func (*LocalIndex) SchemeString ¶
func (p *LocalIndex) SchemeString() string
SchemeString returns a Scheme-style string representation.
func (*LocalIndex) String ¶
func (p *LocalIndex) String() string
String returns a string representation in "slot:depth" format.
func (*LocalIndex) Up ¶
func (p *LocalIndex) Up() int
Up returns the depth (number of parent frames to traverse).
type ModuleInstance ¶
type ModuleInstance struct {
Env *EnvironmentFrame
Exports map[string]*GlobalIndex
}
ModuleInstance represents a loaded and initialized library.
type NamedSlot ¶ added in v1.20.0
NamedSlot pairs a name with one live binding of it. A name can own several slots (hygiene-distinct binders, or the same name at different coordinates), so an enumeration that must not silently drop one yields pairs rather than a map.
type Namespace ¶
type Namespace struct {
// Name is an optional descriptive name (e.g., "interaction-environment").
Name string
// contains filtered or unexported fields
}
Namespace represents a complete Wile VM instance. It owns per-instance syntax interning, phase registry, and library registry. This enables multiple independent Wile VMs in a single Go process.
Design: Namespace is the root of the environment hierarchy. Each EnvironmentFrame holds a reference back to its Namespace to access shared resources (syntax interning, phases, libraries).
Field inheritance policy ¶
Child namespaces (NewChildNamespace and NewSchemeReportNamespace) inherit fields from their parent in one of three ways. New fields MUST pick a policy explicitly — the existing per-field decisions are encoded here, not in the constructors.
Per-VM (each namespace has its own; no inheritance):
Name, parent, phases, runtime, moduleInstances, syntaxInterns
Note: syntaxInterns is nil in children; InternSyntax delegates
to parent so symbol identity remains globally consistent.
Captured at construction (the child stores its own copy of the
parent's pointer or map header at fork time; later *reassignments*
on the parent — e.g. parent.SetRegistry(other) — do not flow to
existing children. Mutations *through* the captured pointer — e.g.
parent.Registry().AddPrimitive(...) — ARE visible to children
because the pointer they hold is the same Go object):
libraryRegistry, libraryEnvFactory, registry, authorizer, envMap
Rationale: these are capability state. Reassignments on the
parent must not silently widen capability on existing children
(envMap is the load-bearing example — see the SetEnvMap doc
comment in this file for the discussion).
Delegated to root via root() walk (the field lives only on the root
namespace; children reach it through the parent chain in O(depth)):
fileResolver, scopeRegistry, immutableTopLevel, contractEnforcement
Note: immutableTopLevel is delegated on the WRITE side only — its
reader short-circuits to false for any child, because interaction
and eval scratch namespaces are deliberately mutable.
contractEnforcement delegates SYMMETRICALLY: it is not a property
of the program's mutability model, and no child reads it today
(bootstrap.NewProfileEnvironment passes no ApplyOptions at all).
Pointer-shared via *EngineServices (allocated once in NewNamespace;
every child receives the same pointer at construction — no root() walk,
one struct, one optional mutex for the lazy-cache block):
ioState, formRegistry, inlineThreshold, maxExpandDepth, exportIndex —
add new engine-lifetime services here (add a field on EngineServices +
two Namespace accessors; children inherit automatically because they
copy the services pointer at construction)
Per-namespace, owned outright (never inherited; each namespace builds
its own, and a child's is unrelated to its parent's):
sealedWriteRoot, inlineHOFTemplates, effectiveRegistry, extensionState
Adding a new field: choose a policy above, document it in this block, then:
- Captured: copy it from the parent in *both* NewChildNamespace and NewSchemeReportNamespace (these are the two constructors that populate child state from a parent; they share the same captured set).
- Delegated (root() walk): define accessors through p.root(); do not store it on child namespaces.
- EngineServices tenant: add a field to EngineServices + two Namespace accessors that read/write p.services.field directly; no constructor change needed (children copy the services pointer already).
Do NOT mix the policies for one field — the asymmetry is the bug source the policy table exists to prevent.
func NewNamespace ¶
func NewNamespace() *Namespace
NewNamespace creates a new Namespace. This is the primary entry point for creating an isolated Wile VM instance.
func (*Namespace) AtPhase ¶
func (p *Namespace) AtPhase(phase Phase) *EnvironmentFrame
AtPhase returns the environment for the given phase level, creating it if needed. The argument is a RELATIVE level in THIS namespace's tower, counted from its own runtime (0), not an absolute compilation stage; any int8 is legal, including negatives (-1 is for-template) and levels above 2, which the macro tower mints on demand. See the Phase type.
func (*Namespace) AttachModule ¶
AttachModule copies a module instance from this namespace to the target. Returns an error if the module is not loaded in this namespace.
func (*Namespace) Authorizer ¶
func (p *Namespace) Authorizer() security.Authorizer
Authorizer returns the security authorizer for this namespace.
func (*Namespace) BoundNamesAcrossPhases ¶
BoundNamesAcrossPhases returns a sorted, deduplicated list of every binding name this namespace holds anywhere: every phase, sealed and mutable alike. Unlike BoundSymbolNames — which spans phase 0 only, returning a Scheme list for the bound-names primitives — this also reports names bound at the expand and compile phases, so macro and special-form keywords appear. It is the set a REPL wants for tab completion. The output is sorted for determinism.
func (*Namespace) BoundSymbolNames ¶
BoundSymbolNames returns a freshly-consed list of every symbol bound at phase 0 in this namespace, spanning BOTH the mutable tier (user defines) and the sealed one (primitives + sealed stdlib procedures) — the probe merges them, so a name shadowed in both appears once. It is the shared body of the environment-bound-names and namespace-bound-names primitives. The sealed tier must be included or primitives like `car` vanish from the result. Iteration order is unspecified.
AmbientKeysAt, not Keys: the listing reports only names resolvable under the ambient (empty) scope set, which is what both read families this listing serves look up — environment-ref/environment-bound? and namespace-ref/namespace-bound? all pass AmbientScopes. Listing a macro-introduced binder would break the listing's primary use, enumerate-then-dereference, on exactly the names it added. Moving any of those reads back to a wildcard silently re-opens that gap, since nothing here can detect it.
func (*Namespace) Compile ¶
func (p *Namespace) Compile() *EnvironmentFrame
Compile returns this namespace's PhaseCompile view, creating it if needed. This is the fixed registry coordinate where syntax compilers live, not a rung of the macro tower — see the Phase type.
func (*Namespace) ContractEnforcement ¶ added in v1.20.0
ContractEnforcement reports whether primitives bound into this namespace carry argument validators built from their specs' ParamTypes. It is the single source of truth for all three binding sites — the base environment, library environments, and post-construction Engine.RegisterPrimitive — which is what keeps them from disagreeing.
Unlike ImmutableTopLevel this delegates to root on BOTH sides: enforcement is not a property of the program's mutability model, so a child has no reason to answer differently from its root.
func (*Namespace) DeleteExtensionState ¶
DeleteExtensionState removes any namespace-scoped extension state stored under key. Safe for concurrent use.
func (*Namespace) EffectiveAuthorizer ¶ added in v1.20.0
func (p *Namespace) EffectiveAuthorizer() security.Authorizer
EffectiveAuthorizer returns the policy that governs an operation initiated from this namespace: the intersection of the ROOT namespace's authorizer and this namespace's own, most-restrictive-wins, which is the project's recorded composition rule and ships as security.All.
Authorizer() deliberately stays an own-field read. Making that getter root-delegate would silently answer "the root's" at every gate site, which is the opposite of what a child with a stricter policy is asking for.
A composite is never built over a nil member: compositeAuthorizer.Authorize calls every member unconditionally and would nil-deref, and a default engine has no authorizer anywhere.
When a child merely copied its parent's authorizer — the norm, since every production NewChildNamespace call site passes no options — the composite calls the same authorizer twice. That is a redundant call, not a semantic change, and it is why a recording authorizer must not be asserted against an exact call count.
func (*Namespace) EffectiveRegistry ¶ added in v1.19.0
EffectiveRegistry returns the narrowed registry the visible top level was bound from, or nil when nothing narrowed it (in which case Registry is the effective surface). The concrete type is *registry.PrimitiveRegistry.
func (*Namespace) EnvMap ¶
EnvMap returns the virtual environment variable map for this namespace, or nil if none has been configured. When non-nil, envvars primitives read from this map instead of the process environment, bypassing the authorizer gate.
func (*Namespace) Expand ¶
func (p *Namespace) Expand() *EnvironmentFrame
Expand returns this namespace's level-1 environment, creating it if needed. This is where a TOP-LEVEL define-syntax stores its transformer. A climb from an arbitrary frame belongs on EnvironmentFrame.NextPhase(), not here.
func (*Namespace) ExportIndex ¶
ExportIndex returns the cached library export index and whether a build has been attempted. Returns (nil, false) if no build has run. Reads the shared EngineServices (one per engine tree). The concrete type is *compilation.LibraryExportIndex.
func (*Namespace) ExtensionState ¶
ExtensionState returns the namespace-scoped state stored under key, and a boolean reporting whether any value was present. Extensions use this for per-Namespace storage whose lifetime is tied to the Namespace. The key is extension-chosen; an unexported sentinel type avoids cross-extension collisions. Safe for concurrent use.
func (*Namespace) FileResolver ¶
func (p *Namespace) FileResolver() FileResolver
FileResolver returns the file resolver for include/load operations. Returns nil if no resolver has been set. Delegated to root.
func (*Namespace) FormRegistry ¶
FormRegistry returns the per-engine forms registry (an *forms.FormRegistry), or nil if unset. Opaque here because environment/ sits below internal/forms in the layering. Reads the shared EngineServices (one per engine tree).
func (*Namespace) IOState ¶
IOState returns the per-engine I/O extension state, or nil if unset. Reads the shared EngineServices (one per engine tree).
func (*Namespace) ImmutableTopLevel ¶
ImmutableTopLevel reports whether top-level-define immutability is enforced for THIS namespace. It is a property of the engine's PRIMARY (root) namespace only — the home of compiled-program top-level defines, which are the frame-reclaim optimizer's Stable anchors. CHILD namespaces (parent != nil) are mutable interaction/eval scratch spaces — (environment ...), scheme-report-environment, profile children — modeled on Chez's mutable interaction-environment (see plans/2026-06-13-immutable-toplevel-by-default-scoping.local.md:357-370): a define there shadows/redefines freely and its bindings are never stamped Stable. This is the "compilation units only" scope: immutability is for the compiled program, not for interactive eval. Reclaim soundness is preserved because set! of a Stable anchor copied into a child is still rejected by the set!-gate, which keys on IsStable() directly (compile_validated.go) rather than on this flag.
func (*Namespace) InlineHOFTemplates ¶
func (p *Namespace) InlineHOFTemplates() InlineHOFTemplateStore
InlineHOFTemplates returns the per-Namespace inline-HOF template store, or nil if templates have not been built for this Namespace (in which case the compiler performs no inline-HOF specialization).
func (*Namespace) InlineThreshold ¶
InlineThreshold returns the engine's configured inlining threshold and whether it was set. Reads the shared EngineServices (one per engine tree). A false bool means the namespace was not built by an Engine (e.g. a direct LoadLibrary in a unit test); the caller should fall back to its own default.
func (*Namespace) InternSyntax ¶
func (p *Namespace) InternSyntax(k values.Value, v syntax.SyntaxValue) syntax.SyntaxValue
InternSyntax returns the canonical version of the given syntax value. If an equivalent syntax value has been seen before, it is returned. Otherwise, the value is added to the intern table and returned.
When a parent Namespace exists, interning is delegated to the parent to maintain syntax identity across environments.
This function is thread-safe.
func (*Namespace) LibraryEnvFactory ¶
func (p *Namespace) LibraryEnvFactory() LibraryEnvFactory
LibraryEnvFactory returns the factory for creating library environments. Returns nil if no factory has been set.
func (*Namespace) LibraryRegistry ¶
func (p *Namespace) LibraryRegistry() LibrarySearcher
LibraryRegistry returns the library registry for R7RS library loading. Returns nil if no registry has been set. Callers needing the full *compilation.LibraryRegistry can type-assert.
func (*Namespace) LookupLibraryEnv ¶
func (p *Namespace) LookupLibraryEnv(scope *syntax.Scope) *EnvironmentFrame
LookupLibraryEnv returns the environment associated with the given library scope, or nil if not registered. Delegated to root. This function is thread-safe.
func (*Namespace) MaxExpandDepth ¶
MaxExpandDepth returns the engine's configured expansion-depth bound and whether it was set. Reads the shared EngineServices (one per engine tree). A false bool means the namespace was not built by an Engine (e.g. a direct LoadLibrary in a unit test); the caller should fall back to its own default.
func (*Namespace) ModuleInstance ¶
func (p *Namespace) ModuleInstance(path string) (*ModuleInstance, bool)
ModuleInstance returns the cached module instance for the given path, or (nil, false) if not loaded.
func (*Namespace) NewChildNamespace ¶
func (p *Namespace) NewChildNamespace(opts ...NamespaceOption) *Namespace
NewChildNamespace creates a new Namespace whose syntax interning is delegated to the receiver (the parent).
Ownership structure ¶
The child is a fully independent Namespace with its own:
- EnvironmentFrame (runtime, phase 0) — the child's ROOT VIEW (parent nil), the mutable user scope
- GlobalEnvironmentFrame — its own store: isolated global bindings at every phase, sealed and mutable alike (define, set!, a profile's sealed apply)
- PhaseRegistry — isolated phase hierarchy (expand, compile created on demand)
The child's runtime EnvironmentFrame.namespace points to the child (not the parent), so new global bindings created in the child are keyed against the child's GlobalEnvironmentFrame. This is what provides binding isolation: definitions in the child do not appear in the parent, and vice versa.
Parent Namespace (root)
+-----------------------------------------------+
| syntaxInterns: map[Value]SyntaxValue ◄────────────── all interning
| syntaxInternsMu (mutex) |
| parent: nil |
| phases: *PhaseRegistry ──► {0: envP} |
| runtime: envP ─────────────────────────────┐ |
| libraryRegistry: LibrarySearcher | |
+--------------------------------------------│--+
│
▼
EnvironmentFrame (envP, phase 0)
+-------------------------------+
| global: *GlobalEnvFrame ───┐ |
| namespace: ──► parent NS | |
+---------------------------│---+
▼
GlobalEnvironmentFrame
+-------------------------+
| keys: {x:0, y:1, ...} |
| bindings: [...] |
+-------------------------+
Child Namespace (returned by this method)
+-----------------------------------------------+
| syntaxInterns: nil (never accessed) |
| parent: ──► parent NS (interning delegate) |
| phases: *PhaseRegistry ──► {0: envC} |
| runtime: envC ─────────────────────────────┐ |
| libraryRegistry: ──► same pointer as parent| |
+--------------------------------------------│--+
│
▼
EnvironmentFrame (envC, phase 0)
+-------------------------------+
| global: *GlobalEnvFrame ───┐ |
| namespace: ──► child NS | |
+---------------------------│---+
▼
GlobalEnvironmentFrame
+-------------------------+
| keys: {} (empty) |
| bindings: [] |
+-------------------------+
envC has NO parent: it is a VIEW, and its store's
sealed tier starts empty. A profile apply through
envC.SealedWriteViewAt(0) is what fills it — which
is why (environment '(wile console)) refuses
namespace-undefine! on car and (environment
'(scheme base)) allows it.
Interning delegation ¶
The child stores a parent pointer and has nil interning maps. InternSyntax checks for a non-nil parent and delegates recursively, ultimately reaching the root Namespace where the maps and mutexes live. This avoids sharing map pointers across structs with independent mutexes (which would be a data race).
Inherited state ¶
The child inherits the parent's libraryRegistry (a LibrarySearcher, concretely *compilation.LibraryRegistry) by value copy. This allows the child to load libraries via (import ...) without requiring the caller to set the registry explicitly. The registry itself is a shared pointer; mutations to the registry (e.g., registering a new library) are visible to both parent and child.
The child also inherits the parent's envMap (virtual environment variable map) by reference. envMap is capability state — it constrains what the envvars primitives can read — so derived namespaces must not silently widen capability by acquiring a nil map that falls through to os.Getenv. The reference is safe to share because SetEnvMap always reassigns the field rather than mutating the existing map.
Contrast with NewChildRuntime ¶
NewChildRuntime returns an *EnvironmentFrame that shares the parent's Namespace directly (same pointer). It is used for library loading, where the library environment should share the same Namespace for syntax interning. However, because it shares the Namespace, it cannot be returned as a standalone environment value — calling Runtime() on the shared Namespace returns the parent's runtime frame, not the child's.
NewChildRuntime: NewChildNamespace:
Namespace (shared) Parent NS Child NS
+------------------+ +----------+ +----------+
| runtime: envP | | runtime: | | runtime: |
+------------------+ | envP | | envC |
│ +----------+ +----------+
│ │
┌────┴────┐ ▼
▼ ▼ EnvironmentFrame (envC)
envP envC ◄── new child +----------------------+
(parent (has own Global- | namespace: child NS |
frame) EnvFrame; reaches +----------------------+
shared NS via the
owning EnvFrame)
envC.Namespace() == parent envC.Namespace() == child
parentNS.Runtime() returns envP child.Runtime() returns envC ✓
NewChildNamespace returns a new *Namespace that can be passed as a first-class Scheme value (e.g., returned from the (environment) primitive and accepted by eval). Its Runtime() returns the child's own runtime frame, and its AtPhase/Expand/Compile methods create phase environments scoped to the child.
Usage ¶
Used by PrimEnvironment and PrimNullEnvironment (R7RS §6.12) to create environments that are identity-compatible with the caller's symbol table while providing isolated bindings. Optional NamespaceOption arguments override fields that would otherwise be inherited from the parent (currently registry and authorizer); see WithChildRegistry and WithChildAuthorizer.
All captured fields (libraryRegistry, libraryEnvFactory, registry, authorizer, envMap) are copied from the parent in one place — adding a new captured field requires a single edit here, not a sweep of multiple constructors.
func (*Namespace) NewChildRuntime ¶
func (p *Namespace) NewChildRuntime() *EnvironmentFrame
NewChildRuntime creates a new library environment that shares this Namespace for syntax interning, but is a full OWNER of its own: its own store and its own PhaseRegistry, hence its own views. It returns the library's ROOT VIEW.
The result has the SAME shape a namespace has, because it is built by the same constructor. Its store's SEALED tier holds the registry apply — primitives, bootstrap procedures, syntax compilers — and its mutable tier holds the library's own defines, at whatever phase each was written.
Phase separation is key disjointness in that store: a phase-1 read admits only phase-1 and ambient slots, so library phase-1 code reaches primitives and does NOT reach the library's phase-0 defines — the hermeticity cut, matching the top level. A flat frame could not express that: it held primitives and user defines together, so there was no way to see the first without the second, and a for-syntax body that lost the defines lost car and list with them. See plans/2026-08-04-library-phase-isolation-{design,impl}.local.md.
Nothing else has to change to FILL the sealed tier: LoadBootstrapCore already routes the registry apply through env.SealedWriteViewAt(PhaseRuntime) and the expanders through SealedWriteViewAt(PhaseExpand), and registry.Apply's WithRuntimeTarget seats the binding through the sealed-write view while the ForeignClosure still captures the ordinary root view, so a primitive resolves user code against the library's own defines. Bootstrap macros reach (1, sealed) by the same route they do for a namespace: they compile with env == the sealed-write root, and AtPhase's sealed climb sends NextPhase() to the phase-1 sealed-write view.
func (*Namespace) NewSchemeReportNamespace ¶
NewSchemeReportNamespace creates a new Namespace that is distinct from the receiver (so eq? returns #f) but contains a snapshot of the receiver's current global bindings at the time of the call.
This implements R7RS §6.12 scheme-report-environment semantics: the returned environment is a separate object from interaction-environment and contains the standard bindings. User definitions added after this call are NOT visible in the returned environment.
func (*Namespace) Phases ¶
func (p *Namespace) Phases() *PhaseRegistry
Phases returns the phase registry.
func (*Namespace) RegisterLibraryScope ¶
func (p *Namespace) RegisterLibraryScope(scope *syntax.Scope, env *EnvironmentFrame)
RegisterLibraryScope associates a library scope with its defining environment. This enables cross-library macro hygiene: when a symbol carries a library scope, the compiler can redirect binding lookup to the library's env. Delegated to root: the registry always lives on the root Namespace.
This function is thread-safe.
func (*Namespace) Registry ¶
Registry returns the primitive registry. The caller must type-assert to *registry.PrimitiveRegistry.
func (*Namespace) Root ¶ added in v1.20.0
Root returns the topmost Namespace on the parent chain: for a child minted by (environment …) / scheme-report-environment / eval's fresh top level, the engine's own Namespace; for that Namespace, itself.
It is the exported form of root, for state whose lifetime is the ENGINE rather than the environment. Extension state stored under SetExtensionState is per-Namespace by design (the SAT model wants exactly that), but a resource tracker read by Engine.Close must be found again from the engine's runtime frame, so it keys on Root().
func (*Namespace) Runtime ¶
func (p *Namespace) Runtime() *EnvironmentFrame
Runtime returns the runtime phase environment (phase 0) — this namespace's ROOT VIEW, where top-level bindings live.
func (*Namespace) SchemeString ¶
SchemeString returns the Scheme representation of the environment.
func (*Namespace) SetAuthorizer ¶
func (p *Namespace) SetAuthorizer(auth security.Authorizer)
SetAuthorizer sets the security authorizer for this namespace.
func (*Namespace) SetContractEnforcement ¶ added in v1.20.0
SetContractEnforcement enables or disables argument validation for primitives bound into this namespace. Set once at namespace construction (wile.WithContractEnforcement); the flag then travels with the namespace, so two engines over one namespace share it.
Not safe to call once the namespace is in use. The field is unlocked and ContractEnforcement is read by Engine.RegisterPrimitive, which an embedder may call at any point in an engine's life, including with SRFI-18 threads running; a later write is then a data race. A green -race run is not evidence against this — the detector reports only the interleavings a run happens to take. SetImmutableTopLevel carries the same contract. Routing both through an atomic cell, as Binding.meta was, would make it enforceable rather than documented.
func (*Namespace) SetEffectiveRegistry ¶ added in v1.19.0
SetEffectiveRegistry records the narrowed registry the visible top level was bound from. Set once during bootstrap, after strict-namespace reduction and any dialect narrowing.
func (*Namespace) SetEnvMap ¶
SetEnvMap sets the virtual environment variable map. When set, envvars primitives read from this map instead of os.Getenv.
The provided map is defensively copied so that subsequent mutation by the caller does not leak into the VM's sandbox state. A nil argument clears the virtual map (falls back to os.Getenv, gated by the authorizer).
Note: EnvMap() still returns the internal map by reference for zero-cost primitive access. Callers who reach for EnvMap() must treat the result as read-only; mutating it bypasses the defensive copy applied here.
func (*Namespace) SetExportIndex ¶
SetExportIndex stores the library export index and marks it as built, preventing subsequent build attempts. Writes the shared EngineServices.
func (*Namespace) SetExtensionState ¶
SetExtensionState stores namespace-scoped extension state under key. Safe for concurrent use.
func (*Namespace) SetFileResolver ¶
func (p *Namespace) SetFileResolver(resolver FileResolver)
SetFileResolver sets the file resolver for include/load operations. Delegated to root: the resolver always lives on the root Namespace.
func (*Namespace) SetFormRegistry ¶
SetFormRegistry stores the per-engine forms registry. The value is opaque here (an *forms.FormRegistry owned by internal/forms).
func (*Namespace) SetIOState ¶
SetIOState stores the per-engine I/O extension state on the shared EngineServices. The value is opaque here (an *io.State owned by extensions/io); this package sits below extensions/io in the layering and never inspects it.
func (*Namespace) SetImmutableTopLevel ¶
SetImmutableTopLevel enables or disables top-level-define immutability for the engine. Set once at engine construction (WithImmutableTopLevel / WithMutableTopLevel). Stored on the root; child namespaces ignore it and are always mutable (see ImmutableTopLevel).
Not safe to call once the namespace is in use, for the reason spelled out on SetContractEnforcement.
func (*Namespace) SetInlineHOFTemplates ¶
func (p *Namespace) SetInlineHOFTemplates(store InlineHOFTemplateStore)
SetInlineHOFTemplates installs the inline-HOF template store. Called once per Namespace at bootstrap, after the sealed base is loaded.
func (*Namespace) SetInlineThreshold ¶
SetInlineThreshold stores the engine's configured procedure-inlining threshold (WithInlineThreshold) on the shared EngineServices. Set once at engine build so runtime-triggered library compilation can honor it. An explicit 0 (inlining disabled) is retained and distinguished from "never set" via the bool returned by InlineThreshold.
func (*Namespace) SetLibraryEnvFactory ¶
func (p *Namespace) SetLibraryEnvFactory(f LibraryEnvFactory)
SetLibraryEnvFactory sets the factory for creating library environments.
func (*Namespace) SetLibraryRegistry ¶
func (p *Namespace) SetLibraryRegistry(registry LibrarySearcher)
SetLibraryRegistry sets the library registry for R7RS library loading.
func (*Namespace) SetMaxExpandDepth ¶
SetMaxExpandDepth stores the engine's configured macro-expansion recursion bound (WithMaxExpandDepth) on the shared EngineServices. Set once at engine build so runtime-triggered library compilation can honor it. An explicit 0 (bound disabled / unlimited) is retained and distinguished from "never set" via the bool returned by MaxExpandDepth.
func (*Namespace) SetModuleInstance ¶
func (p *Namespace) SetModuleInstance(path string, inst *ModuleInstance)
SetModuleInstance caches a loaded module instance.
func (*Namespace) SetRegistry ¶
SetRegistry sets the primitive registry.
func (*Namespace) Store ¶ added in v1.20.0
func (p *Namespace) Store() *GlobalEnvironmentFrame
Store returns this namespace's one binding store: every global binding it holds, at every phase, sealed and mutable alike. The phase environments are views over it, so reaching a binding "in another phase" is a coordinate on the query rather than a different object.
The type is still GlobalEnvironmentFrame — the accessor is named for what the object now IS; renaming the type is deferred (design Q3).
func (*Namespace) SyntaxInternCount ¶
SyntaxInternCount returns the number of interned syntax objects. This is intended for testing and debugging purposes.
On a child Namespace this returns 0: children have no map of their own and delegate interning to the parent. Call it on the root.
type NamespaceOption ¶
type NamespaceOption func(*namespaceConfig)
NamespaceOption configures a derived namespace at construction time. Use it with NewChildNamespace to override fields that would otherwise be inherited from the parent (e.g. a restricted registry or a different authorizer).
The "*Set" booleans on namespaceConfig let WithChildRegistry(nil) and WithChildAuthorizer(nil) mean "explicitly set the field to nil" — distinct from "no override supplied" which means "inherit from parent."
func WithChildAuthorizer ¶
func WithChildAuthorizer(a security.Authorizer) NamespaceOption
WithChildAuthorizer overrides the parent's security authorizer on a derived namespace. Use this to give a child a stricter (or different) capability profile.
Distinct from wile.WithAuthorizer, which configures an EngineOption at the top level.
func WithChildRegistry ¶
func WithChildRegistry(r any) NamespaceOption
WithChildRegistry overrides the parent's primitive registry on a derived namespace. Use this to install a restricted or alternative registry in a child without mutating the parent.
Distinct from wile.WithRegistry, which configures an EngineOption at the top level — the wile.* and environment.* option families operate on different config types and have different semantics.
type OriginRef ¶ added in v1.19.0
OriginRef identifies the provenance ROOT of a binding with library identity: the defining library's KEY (RootLib) plus the DEFINING name inside it (RootName, invariant to any export/import renaming). It is value-identity: a library define and every import of it, however renamed or re-exported, carry equal OriginRefs. Set once (library finalization for a define, propagation at import) and never mutated, so it is safe to share across the copy-on-write BindingMeta path. A nil *OriginRef means NO library identity: a program-top-level (define ...), never imported, so only ever compared as the identical object. Identity assumes RootLib (a LibraryName.Key()) names its library uniquely, the same key assumption ScopeKey/FreeIdKey rely on.
type Phase ¶
type Phase int8
Phase is a RELATIVE index, not an absolute stage of compilation. It counts levels from the owner's OWN runtime, which is always 0. Each owner — a Namespace, or a NewChildRuntime library env — has its own PhaseRegistry, so level 1 of a library and level 1 of the top level are different frames wearing the same number.
Every operation that MOVES along the axis is relative, and none of them name a constant: NextPhase() is phaseLevel+1, import composes env.PhaseLevel() with the import set's shift (compilation.composePhaseShift), and definitionFallbackPhases descends from the frame's own level. The constants below are the levels the TOP level occupies. They are not the set of levels that exist — GetOrCreate mints a view for any int8, and the macro tower climbs past 2 whenever a transformer body defines a macro of its own — and they are not landmarks a climb may aim at. Reaching for Expand() where NextPhase() belongs collapses the tower into one level, and the mistake is invisible at the top level, where phaseLevel is 0 and the two coincide (level-0 identity).
Phase indexes PhaseRegistry.envs and serves as the typed value for EnvironmentFrame.phaseLevel. The companion type registry.PhaseSet is a bitset over non-negative Phase values used for primitive registration.
ADDING A NEW PHASE requires updates in these locations:
- environment/phase_registry.go (this file) — add a Phase constant and a String() case.
- registry/phase.go — add the matching PhaseSet<Name> bit constant if the new phase is representable in a PhaseSet (i.e. phase ≥ 0 and phase < phaseSetBits). The init() assertion verifies the bit position matches the Phase index.
- registry/apply.go — extend phaseTargets if primitives may register at the new phase.
- wile/options.go — re-export so embedders can name the constant.
const ( PhaseTemplate Phase = -1 // for-template: one level BELOW the owner's runtime PhaseRuntime Phase = 0 // the owner's own runtime — the origin the rest count from PhaseExpand Phase = 1 // one level up: where a TOP-LEVEL define-syntax stores PhaseCompile Phase = 2 // the special-form registry coordinate; see below )
type PhaseKey ¶ added in v1.20.0
type PhaseKey struct {
// contains filtered or unexported fields
}
PhaseKey is a Phase plus an explicit ANY wildcard. Phase is a full int8 domain (GetOrCreate mints any value; the tower climbs to 127), so there is no free in-band value to steal, and per [nil means NONE] the wildcard is a named value, never a sentinel.
The fields are unexported and the two constructors are the only way to build one. PhaseKey is compared with ==, so an exported level alongside an exported wildcard would make {level: 3, wildcard: true} constructible — a key that AnyPhase() would never equal, and that every == in this file would therefore read as a phase-3 exact key while tierOf classified it as ambient. Keeping the denormalized state unrepresentable is cheaper than checking for it.
func AnyPhase ¶ added in v1.20.0
func AnyPhase() PhaseKey
AnyPhase returns the ambient wildcard key: visible from every phase. Only sealed entries may carry it — see CreateGlobalBindingAt.
func ExactPhase ¶ added in v1.20.0
ExactPhase returns the key for an exact phase.
type PhaseRegistry ¶
type PhaseRegistry struct {
// contains filtered or unexported fields
}
PhaseRegistry manages phase-indexed environment frames. It provides O(1) access to any phase environment and supports lazy creation of phase environments on first access.
The registry is owned by the Namespace and shared across all child environments via pointer. This enables any environment frame to access any phase directly.
Thread-safe: All operations are protected by a read-write mutex to support concurrent macro expansion.
func (*PhaseRegistry) Get ¶
func (p *PhaseRegistry) Get(phase Phase) *EnvironmentFrame
Get returns the environment for the given phase, or nil if not yet created.
func (*PhaseRegistry) GetOrCreate ¶
func (p *PhaseRegistry) GetOrCreate(phase Phase) *EnvironmentFrame
GetOrCreate returns the environment for the given phase, creating it if needed. Phase 0 always returns the owner's root view. Other phases are lazily minted as views over the SAME store — the map is a view cache, and AtPhase must keep returning a stable pointer per (owner, phase) because local expand envs chain off these frames and code compares frames by pointer.
func (*PhaseRegistry) Namespace ¶
func (p *PhaseRegistry) Namespace() *Namespace
Namespace returns the owning Namespace.
func (*PhaseRegistry) Phases ¶
func (p *PhaseRegistry) Phases() []Phase
Phases returns all currently instantiated phase levels. Useful for debugging and introspection.
func (*PhaseRegistry) TopLevelFrame ¶
func (p *PhaseRegistry) TopLevelFrame() *EnvironmentFrame
TopLevelFrame returns the runtime (phase 0) environment frame.