feat: #set property accessors (write counterpart of #get)
A method `name :: (self: *T, value: V) #set { ... }` (or `=> expr;`) is the
write counterpart of a `#get` accessor: `obj.name = rhs` dispatches to it as
`obj.name(rhs)` when no real field matches. Plumbed parallel to `#get`:
- lexer/token `#set`; `FnDecl.is_set` + `Function.is_set`; parsed in the same
marker slot as `#get` (no return type, exactly self + one value param).
- get+set coexistence: a setter registers/mangles/dispatches under an effective
`name$set` name (`$` is illegal in sx identifiers, so unmistakable), keeping a
same-name `#get` under the plain `name`. Resolution is declaration-order-
independent: a plain read query picks the non-setter, a `name$set` write query
picks the setter (accessorEffName / accessorNameMatches / structMethodFn).
- write dispatch in lowerAssignment via tryLowerPropertyAssignment: plain assign
synthesizes `obj.name$set(rhs)`; compound `OP=` is get-modify-set and
evaluates the receiver EXACTLY ONCE (bound to a synthetic local); read-only
(#get-only) and write-only (#set-only + compound) emit clear diagnostics; a
real field of the same name still wins. Multi-assign property targets dispatch
the setter too (tryLowerPropertyStore, via a pre-lowered-Ref binding).
Payoff: List gains a `len` #set, so `xs.len = n` works; the `.items.len = N`
write workarounds in sched.sx + ui/* + platform/* revert to `xs.len = N`.
issues/0160 records an optional-chain interaction surfaced by the review (a
pre-existing `?T` value-optional read miscompile that blocks getter-through-`?.`).
This commit is contained in:
@@ -1958,12 +1958,25 @@ pub const Parser = struct {
|
||||
return_type = try self.parseTypeExpr();
|
||||
}
|
||||
|
||||
// Optional `#get` property-accessor marker: `name :: (self) -> R #get => expr;`.
|
||||
// The method is invoked via field syntax (`obj.name`) rather than `obj.name()`.
|
||||
// Optional `#get` / `#set` property-accessor marker:
|
||||
// read: `name :: (self) -> R #get => expr;` (invoked via `obj.name`)
|
||||
// write: `name :: (self, value: V) #set { … }` (invoked via `obj.name = rhs`)
|
||||
// The two share the marker slot; a `#set` has no return type (void) and
|
||||
// takes the receiver plus exactly one value parameter.
|
||||
var is_get = false;
|
||||
var is_set = false;
|
||||
if (self.current.tag == .hash_get) {
|
||||
is_get = true;
|
||||
self.advance();
|
||||
} else if (self.current.tag == .hash_set) {
|
||||
is_set = true;
|
||||
self.advance();
|
||||
if (return_type != null)
|
||||
return self.fail("a '#set' accessor returns void — drop the '-> T' return type");
|
||||
// self + exactly one value parameter. `params` here are the value/
|
||||
// receiver params only (type params `$T` are collected separately).
|
||||
if (params.len != 2)
|
||||
return self.fail("a '#set' accessor takes exactly the receiver and one value parameter");
|
||||
}
|
||||
|
||||
// Optional ABI / calling-convention annotation: `abi(.c)` / `abi(.zig)` /
|
||||
@@ -2057,6 +2070,7 @@ pub const Parser = struct {
|
||||
.name_span = name_span,
|
||||
.is_raw = name_is_raw,
|
||||
.is_get = is_get,
|
||||
.is_set = is_set,
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -3775,7 +3789,9 @@ pub const Parser = struct {
|
||||
if (tag == .arrow) return self.hasFnBodyAfterArrow();
|
||||
// `kw_extern`/`kw_export`: a postfix linkage modifier (e.g. `f :: () extern;`
|
||||
// with no return type) marks a fn decl just like `abi(...)`.
|
||||
return tag == .l_brace or tag == .hash_builtin or tag == .fat_arrow or tag == .kw_abi or tag == .kw_extern or tag == .kw_export;
|
||||
// `#set` is a bodied accessor with NO return type, so it sits directly
|
||||
// after `)` (`(self, v) #set { … }`) — a fn-def marker like `{`/`=>`.
|
||||
return tag == .l_brace or tag == .hash_builtin or tag == .fat_arrow or tag == .hash_set or tag == .kw_abi or tag == .kw_extern or tag == .kw_export;
|
||||
}
|
||||
|
||||
fn hasFnBodyAfterArrow(self: *Parser) bool {
|
||||
@@ -3803,6 +3819,7 @@ pub const Parser = struct {
|
||||
if (self.current.tag == .l_brace) return true;
|
||||
if (self.current.tag == .hash_builtin) return true;
|
||||
if (self.current.tag == .hash_get) return true; // `-> R #get => …` is a fn def
|
||||
if (self.current.tag == .hash_set) return true; // `-> R #set { … }` is a fn def
|
||||
if (self.current.tag == .kw_abi) return true;
|
||||
// Postfix linkage modifier after the return type: `-> R extern;` /
|
||||
// `-> R export { … }` (and `-> R abi(.c) extern`). Marks a fn def.
|
||||
|
||||
Reference in New Issue
Block a user