dsc#101: receiver methods bind type parameters on the generic receiver - #105
Merged
Conversation
A receiver method on a generic type can now declare its type parameters
in the receiver position, per the rfd#56 amendment:
fn (s Signal<T>) get() T
fn (s mut Signal<T>) set(next: T) void
T is introduced by the receiver and resolves to the receiver value's
type argument at each call site: signal(42).get() yields int and
signal("x").get() yields string. Declaring the parameter on the method
instead (fn (s Signal) get<T>()) stays parseable — it is the phase-1
spelling, binding positionally — but declaring both is an error, since
the parameter is bound by the receiver, not the method.
- Parser: the receiver type reads an optional type-parameter list with
the declaration grammar, so a parameter may carry a bound:
fn (x Holder<T: Named>) name() string. Mutating receivers bind the
same way: fn (s mut Signal<T>) set(next: T) void.
- Checker: collection validates the receiver is generic, the arity
matches the struct declaration, and the parameters are not also
declared on the method. Receiver-bound parameters are pushed around
signature resolution and body checking, so T in the body and
signature resolves like any other type parameter and obeys the
unbounded-parameter capability rule (no member calls on an
unbounded T). Bounds declared on the struct's own parameters are
inherited where the receiver leaves them unbounded.
- Call sites: the receiver value's type arguments bind the receiver's
parameters by name and substitute into the signature, and a bound
declared on the receiver is verified against the type argument —
construction does not enforce bounds the struct itself does not
declare, so the method call site checks the contract (rfd#56
phase 2). substitute_type now also substitutes a Named whose name
matches a parameter, so a receiver method's T does not survive a
cross-module call as an opaque named type.
- Emission is unchanged — the type parameter never reaches output,
pinned by a transpile test asserting the generic and erased forms
emit byte-identical JavaScript.
Negative fixtures, per the phases 1/2 convention: wrong argument type
at a receiver-method call, a member call on an unbounded receiver T,
a call-site bound violation, and declaration errors (parameters on both
receiver and method, arity mismatch, type arguments on a non-generic
receiver).
Also decided here: explicit type arguments at a construction site
(Signal<T> { value: initial }) are rejected, not supported. The
spelling used to parse < and > as comparison operators and report
unknown identifier T plus cannot compare types; the parser now
recognizes the shape and reports that type arguments are inferred from
the field values, pointing at the inference-only design (rfd#56).
Closes #101. Tracked under #66.
Merged
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 #101. Tracked under #66. Unblocks deka#771 (the
ui/*port), which is blocked onSignal<T>having methods and nothing else.The rule (now stated in the amended RFD 56)
Tis bound by the receiver, not declared on the method:Tis introduced by the receiver type and resolves to the receiver value's type argument at each call site.fn (s Signal) get<T>() Tis wrong — it reads as if the method could pick aTunrelated to the one the receiver holds. The phase-1 positional spelling stays working (it binds positionally at call sites andgeneric_struct_signalpins it), but declaring parameters on both the receiver and the method is now an error.What changed
fn (x Holder<T: Named>) name() string, union bounds included. Mutating receivers bind the same way.Tresolves like any other type parameter — including the phase-1 capability rule: no member calls on an unboundedT. Bounds declared on the struct's own parameters are inherited where the receiver leaves them unbounded (struct Holder<T: Named>+fn (x Holder<T>)still seesT: Named).signal(42).get()→int,signal("x").get()→string). A receiver-declared bound is verified against the type argument, since construction only enforces bounds the struct itself declares.substitute_typenow also substitutes aNamedmatching a parameter name, so a receiver method'sTcan't survive a cross-module call as an opaque named type.Negative fixtures (the ones that matter most)
signal(0).set("nope")→expected argument type \number`, found type `string`` with a span.T→ the normative rfd#56 capability error.type argument \NoName` ... does not satisfy the bound `Named``, at the call site.Signal<T, U>onstruct Signal<T>); type arguments on a non-generic receiver.Also decided: construction-site type arguments are rejected, not supported
Signal<T> { value: initial }used to parse</>as comparison operators and reportunknown identifier Tpluscannot compare types. The parser now recognizes the spelling and rejects it with the design rule: type arguments are inferred from the field values, writeSignal { ... }(rfd#56). Supporting the explicit form would add syntax for no gain — inference already solves every construction the corpus contains.Verification
cargo test --workspace: all 34 suites green (288 deka_syntax unit tests including 9 new ones, cli integration including the emission pin).Testsuite against the pinned deka 0.45.0 runtime:
Six new fixtures under
tests/testsuite/generics/, mirroring the phase 1/2 conventions.-kimi