Finish issue 10770: phantom nominal arguments and shared top-level constants - #10829
Draft
rtfeldman wants to merge 2 commits into
Draft
Finish issue 10770: phantom nominal arguments and shared top-level constants#10829rtfeldman wants to merge 2 commits into
rtfeldman wants to merge 2 commits into
Conversation
Relating a call's request to the callee's checked signature descended into a nominal's backing and returned, never relating the nominal's own declaration arguments. Every argument the backing mentions is reached that way, so only phantom arguments were lost: a `Capability(a)` whose payload never mentions `a` left `a` unresolved in the callee's request, and an unresolved type variable finalizes as uninhabited, so the capability's closures specialized at an uninhabited type and lowered to a reachable runtime error. That is the "hit a runtime error" issue 10770 reports. A declaration's arguments are components of the value the same way a record's fields are, so the traversal now relates them like every other constructor it walks. The fx platform gains `store_seed!`/`take_seed!`, a pair whose result type is the only place its type variable appears, which is what lets a test build a phantom capability without constraining it from a call site.
A top-level binding is one value, so every reference must read the same allocation. A binding whose value contains a callable got neither: the ConstStore's function values were ruled out of static data, so each reference materialized its own erased callable, and a platform handed that binding twice received two pointers. Static data already emits an erased callable as one allocation naming its procedure through a relocation, so a capture-free function value is fully decided by the ConstStore and belongs there. Two conditions make that safe. A restoration reached while an enclosing ConstStore node is still being built reads that node's binding local, which a standalone static initializer procedure cannot name, so it no longer becomes static data. And a request that already names a committed type is sealed before it keys the static-data use, which is what lets two references to one constant share a single allocation instead of interning separately.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #10770. With this, the issue's own reproduction mounts:
roc build --target=wasm32 --opt=sizeofrepro/nightly-2026-08-13-generic-browser-signal/app.rocagainst roc-signals at c0e4a9c, mounted withscripts/browser/mount_wasm_example.mjs, printsmounted minimal, and thelocation-sourceexample fixture passes its spec end to end.Two remaining causes, both independent of #10814 and #10818.
Relating a call's request to the callee's checked signature descended into a nominal's backing and returned, never relating the nominal's own declaration arguments. Every argument the backing mentions is reached that way, so only phantom arguments were lost: a
Capability(a)whose payload never mentionsaleftaunresolved in the callee's request, and an unresolved type variable finalizes as uninhabited, so the capability's closures specialized at an uninhabited type and lowered to a reachable runtime error. That is thehit a runtime errorthe issue reports, and it is why the failure tracked the payload type:Str,Bool,U64andList(U64)worked while{ path : Str },(Str, Str)and[Visible, Hidden]did not. A declaration's arguments are components of the value the same way a record's fields are, so the traversal now relates them like every other constructor it walks.Separately, a top-level binding is one value, so every reference must read the same allocation, and a binding whose value contains a callable got neither: the ConstStore's function values were ruled out of static data, so each reference materialized its own erased callable and a platform handed that binding twice received two pointers. Static data already emits an erased callable as one allocation naming its procedure through a relocation, so a capture-free function value is fully decided by the ConstStore and belongs there. Two conditions make that safe: a restoration reached while an enclosing ConstStore node is still being built reads that node's binding local, which a standalone static initializer procedure cannot name, so it no longer becomes static data; and a request that already names a committed type is sealed before it keys the static-data use, which is what lets two references to one constant share a single allocation. Without the first condition
test/cli/issue_9889_roc_parser/Issue10303Sgf.rocfails ARC certification with an unbound local in a static initializer.test/fx/phantom_capability_parameter.roccovers the first, driven through the io-spec matrix on every backend; it printshit a runtime errorwithout the change. It needs the fx platform's newstore_seed!/take_seed!pair, whose result type is the only place its type variable appears, since nothing else lets a test build a phantom capability without constraining it from a call site.test/provided-callable-hostgains a top-level binding read by both fields of a provided root's result for the second; those two fields arrive 0x40 apart without the change.