Skip to content

Propagate state names through ModuleContext's bind#1062

Open
nanavati wants to merge 2 commits into
B-Lang-org:mainfrom
nanavati:modulecontext-naming
Open

Propagate state names through ModuleContext's bind#1062
nanavati wants to merge 2 commits into
B-Lang-org:mainfrom
nanavati:modulecontext-naming

Conversation

@nanavati

Copy link
Copy Markdown
Collaborator

State naming never worked for Classic (BH) code using ModuleContext or ModuleCollect: BSV's typechecker inserts a setStateName wrapper on every bound module statement, but Classic has no such insertion — the name can only ride the monad's bind, and ModuleContext's bind dropped it. Registers bound in a Classic [ModuleCollect] module came out as ec/ec_1 instead of ec_count/ec_flag.

Commit 1 — Collapse re-naming layers in the module schedule name scope. A module expression can carry more than one state-naming layer (an explicit setStateName, or the BSV typechecker's inserted one, wrapped by a naming layer from a module monad's bind). Instance naming already collapses the layers (the PrimStateName handler re-heads the IStateLoc per layer, innermost name wins), but each handler also pushed one schedule-name-scope frame, leaving a stale frame whose IStateLoc prefix the final rule names do not extend; remIStateLocPrefix then poisons that frame's name map, and a scheduling attribute naming a submodule rule turns it into an internalError. The push now renames the still-open frame in place when it is a re-naming of that same scope; each conjunct of the re-naming condition is pinned by a counterexample test.

Commit 2 — the fix itself. ModuleContext's bind extracts the continuation's parameter name with primGetParamName and wraps the bound computation in setStateName (the standard approach; see ReaderModule in the testsuite). For BSV code the typechecker's wrapper is still present, so one module now carries two stacked naming layers — which commit 1 makes safe.

Tests: a Classic ModuleCollect naming test; a pong-distilled regression (old-style instantiation where the instance name differs from the binder, plus an urgency attribute that forces the name maps); and pins for the collapse condition's non-empty-tail and same-ifc-id conjuncts (a nested primBuildModule, and a user binder squatting on the internal _elements name).

Known limits (unchanged behavior, crash identically before and after): user binders squatting on _elements/_velements, and an explicit setStateName directly around a primBuildModule scope.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QFFuSAof9Rp8Zye97fCpAG

nanavati and others added 2 commits July 7, 2026 19:46
A module expression can carry more than one state-naming layer: for
example, an explicit setStateName (or the one BSV's typechecker inserts
for bound instantiations) wrapped by a naming layer from a module
monad's bind.  The PrimStateName handler re-heads the IStateLoc for
each layer -- a sibling of the previous stack, not a child -- so
instance naming collapses the layers and the innermost name wins.  But
each handler also pushed one schedule-name-scope frame, leaving a stale
frame whose IStateLoc prefix the final rule names do not extend.
remIStateLocPrefix then poisons the frame's name map -- lazily, so the
internalError only fires if something forces the map, such as a
scheduling attribute that names a submodule rule, and only visibly when
the two layers carry different names (e.g. old-style instantiation
where the instance name differs from the binder).

Collapse the layers in pushModuleSchedNameScope too: a push that
re-names the still-open scope described by the frame on top of the
stack renames that frame in place (bumping its ignore count for the
extra pop) instead of stacking a second frame.

The push is a re-naming exactly when all of the following hold; each
condition is pinned by a counterexample in the testsuite:

- the new IStateLoc and the top frame's have equal tails: the same
  parent position.  A child scope's tail is the parent's full stack,
  which is strictly longer, and a completed sibling's frame has
  already been popped, so only a re-heading of the open scope can
  present an equal tail;

- the tails are non-empty: primBuildModule scopes are fresh singleton
  stacks and nest dynamically (the primitive is forced lazily, even
  mid-rule), so two empty tails are two unrelated build-module scopes;

- the heads have the same isl_ifc_id: the same binding.  The
  PrimStateName handler preserves the ifc id when it re-heads the
  stack, while a child that reaches the same tail (by escaping a
  naming level) carries its own;

- the frame has no rules registered and no rule/interface elaboration
  in progress: the scope's body has not begun.  Rule names only reach
  the map in addRules, after rule bodies elaborate, so the
  elabProgress check covers immediate registration;

- no rules are saved for deferred registration: under moduleFix,
  addRules only saves the rules and replays them after the body
  finishes, so a saved rule is already named under the current prefix
  while both frame-local checks pass -- renaming the frame would
  orphan it.

Known limits, unchanged from the previous behavior (each crashes the
compiler identically before and after this change): user binders that
squat on the internal element names ("_elements"/"_velements") hijack
the IStateLoc classification and escape a naming level; and an
explicit setStateName directly around a primBuildModule scope stacks
rather than collapses (blocked by the non-empty-tail condition).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BaLzQyHtX6CvXty92voyM8
State naming never worked for Classic (BH) code using ModuleContext or
ModuleCollect: BSV's typechecker inserts a setStateName wrapper on
every bound module statement, but Classic has no such insertion -- the
name can only ride the monad's bind, and ModuleContext's bind dropped
it.  Registers bound in a Classic [ModuleCollect] module came out as
"ec"/"ec_1" instead of "ec_count"/"ec_flag".

Use the standard approach (see ReaderModule in the testsuite): bind
extracts the continuation's parameter name with primGetParamName and
wraps the bound computation in setStateName.  For BSV code the
typechecker's wrapper is still present, so one module now carries two
stacked naming layers; the previous commit teaches the schedule name
scope to collapse them.

Tests: a Classic ModuleCollect naming test; the pong-distilled
regression (old-style instantiation, where the instance name differs
from the binder, plus an urgency attribute that forces the name maps);
and pins for the collapse condition's non-empty-tail and same-ifc-id
conjuncts (a nested primBuildModule, and a user binder squatting on
the internal "_elements" name).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BaLzQyHtX6CvXty92voyM8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant