A2: @suspending decorator, host-resource method/static scope, receiver binding - #53
Merged
Conversation
…r receiver binding Three related extensions to A1, motivated by one observation: the tier-(c) blocking profile's load-bearing site — [method]pollable.block — is a host-resource METHOD, which A1's plain-functions-only scope did not cover. - suspending() becomes dual-form: the direct call stays canonical (and is the only form possible in record literals), and it now works as a stage-3 method decorator on instance and static methods. The decorator REFUSES what it cannot mark, at class-definition time: non-method kinds (class/getter/setter/accessor/field) throw naming the kind, and the TypeScript-legacy experimentalDecorators calling convention throws with guidance — under it the decorator receives the prototype, and marking that would brand the wrong object and corrupt the property descriptor. (The single-arg A1 form already type-checked in decorator position by arity-assignability; blessing it without the kind checks would have traded a compile error for a silent no-op on resource classes.) - A1 scope extends to host-resource methods and statics. The brand authority for an instance method is the CLASS PROTOTYPE, read at dispatcher-construction time — the Suspending-wrap decision is per-declaration and taken at instantiation, before any instance exists. Instance-level method overrides change the dispatched body, never suspendability. Statics carry the brand on the function itself. Constructors stay unmarkable (synchronous by the C2 amendment). New fixture suspending-method.wat: the smallest host resource with all three member forms ([constructor]/[method]/[static]) — no testdata component imports a true resource method (host-borrow/imported-resource use plain functions taking borrows). - A2 receiver rule: interface members are invoked with their containing object as this, matching the resource static arm's apply(cls). The plain arm previously called extracted functions UNBOUND: a class instance type-checked as an interface provider, worked while stateless, and broke with this === undefined the moment a method touched instance state — the silent liberal-acceptance failure the contract forbids (and it would have made a parked @suspending method that reads its own state a cruel joke). World-level bare imports have no containing object and stay unbound. contracts/embedder-api.md: Status gains A2; the A1 bullet's scope and spelling rules updated. Pins (tests/embedder/suspending_imports_test.ts, now 14): decorated provider-class method parks with this bound; receiver binding alone (unmarked, stateful, plain mode); decorated resource method parks (probe(21) == 42 — the pollable.block shape) and decorated static parks, with the guest-driven constructor staying synchronous; getter decoration throws naming the kind; the legacy convention throws pointing at stage-3. Gates: test-runtime 350/0; conformance 1254/0 (0 unexpected, 0 stale); sched-seeds, shells, test-wasi-shims/ct-runner/bundle/ports/webrtc green; smoke-tls suites PASS; websocket-conformance green (consumer tree clean before/after); iroh-exam 4/5 with the documented pre-existing scenario-3 environmental.
The chunking rationale cited 'the WIT' as requiring exactly len bytes — true for the @0.2 track this fragment serves, but wasi:random@0.3.0 (post-consolidation authority: WebAssembly/WASI proposals/random) renames len to max-len and permits short reads, with callers required to loop and a >=1-byte floor. Scope the exact-len claim to 0.2 and record where 0.3 differs, so a future @0.3 fragment inherits neither rule blindly — in either direction. The archived wasi-random repo's 0.3 rc still carries the old exact-len text; cite the moved authority. Behavior unchanged: chunk-to-full conforms on both tracks.
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.
Follow-on to #51 (A1), triggered by two questions from review discussion: can
suspendingbe a decorator, and why isn't the canonical import shape a class?The load-bearing part: A1 scoped marking to plain functions — but
[method]pollable.block, the site the tier-(c) WASI blocking profile exists for, is a host-resource method. A2 extends the scope: instance methods are marked on the class (prototype = per-declaration brand authority, read at wrap time; instance overrides swap the body, never suspendability), statics on the function itself, constructors never (C2).Decorator:
suspendingnow works as a stage-3 method decorator (@suspending read() {…}) alongside the canonical call form. It refuses loudly rather than no-op: non-method kinds throw at class-definition time, and the legacyexperimentalDecoratorsconvention throws with guidance (it would receive the prototype — branding the wrong object and corrupting the descriptor). Note the A1 single-arg form already type-checked in decorator position by arity-assignability; without kind checks that was a compile-pass/silent-no-op trap.Receiver binding (the class-provider defect): the plain dispatch arm called extracted interface members unbound while the static arm bound
cls— so a class instance type-checked as an interface provider and broke withthis === undefinedon first state access. A2 rule: interface members get their containing object as receiver; bare world-level imports stay unbound. Records remain the canonical type; class instances become a properly supported spelling.New fixture
suspending-method.wat(smallest host resource with all three member forms — no existing testdata component imports a true[method]). 14 pins total insuspending_imports_test.ts.Second commit:
wasi-shims/random.tscomment scopes the exact-lenclaim to the 0.2 track and recordswasi:random@0.3.0's short-read latitude (post-consolidation authorityWebAssembly/WASI proposals/random; the archived repo's rc still shows exact-len). Behavior unchanged — chunk-to-full conforms on both tracks.Gates: test-runtime 350/0; conformance 1254/0, 0 unexpected, 0 stale; sched-seeds, shells, wasi-shims/ct-runner/bundle/ports/webrtc green; smoke-tls PASS; websocket-conformance green (consumer tree verified clean); iroh-exam 4/5 (documented pre-existing scenario-3 environmental).
Next: Track 2 proper — the parking-pollable kernel (blocking profile) over this surface.