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:
@@ -0,0 +1,17 @@
|
||||
// Writing to a `#get`-only property (no matching `#set`) is rejected with a
|
||||
// clear "read-only" diagnostic — not the generic "field not found" the bare
|
||||
// struct-store path would emit. (The write counterpart, a `#set`-only
|
||||
// property, accepts plain assignment but rejects compound `+=` because there is
|
||||
// no `#get` to read the current value.)
|
||||
#import "modules/std.sx";
|
||||
|
||||
Reading :: struct {
|
||||
raw: i64 = 0;
|
||||
doubled :: (self: *Reading) -> i64 #get => self.raw * 2;
|
||||
}
|
||||
|
||||
main :: () -> i64 {
|
||||
r : Reading = .{ raw = 5 };
|
||||
r.doubled = 10; // ERROR: property 'doubled' is read-only (no '#set')
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: property 'doubled' is read-only (no '#set')
|
||||
--> examples/diagnostics/1193-diagnostics-readonly-property-write.sx:15:5
|
||||
|
|
||||
15 | r.doubled = 10; // ERROR: property 'doubled' is read-only (no '#set')
|
||||
| ^^^^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// live element count, so a List is directly iterable with a `for`-each, and
|
||||
// `xs.len` reads the live count via a `#get` accessor. Exercises append (incl.
|
||||
// a realloc past the initial cap of 4), for-each, parallel for-with-index,
|
||||
// empty iteration, direct `for xs` over the List, and truncation via items.len.
|
||||
// empty iteration, direct `for xs` over the List, and truncation via `xs.len = 0`.
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> i64 {
|
||||
@@ -33,8 +33,8 @@ main :: () -> i64 {
|
||||
while j < xs.len { acc = acc + xs.items[j]; j = j + 1; }
|
||||
print("indexed sum={}\n", acc); // 210
|
||||
|
||||
// truncate to empty via items.len, then iterate (zero iterations)
|
||||
xs.items.len = 0;
|
||||
// truncate to empty via the `len` #set accessor, then iterate (zero iters)
|
||||
xs.len = 0;
|
||||
cnt := 0;
|
||||
for xs.items (e) { cnt = cnt + 1; }
|
||||
print("after trunc: len={} iters={}\n", xs.len, cnt); // len=0 iters=0
|
||||
|
||||
21
examples/memory/0841-memory-list-len-set.sx
Normal file
21
examples/memory/0841-memory-list-len-set.sx
Normal file
@@ -0,0 +1,21 @@
|
||||
// `List(T).len` is a `#get`/`#set` property pair: `xs.len` reads the live
|
||||
// element count (delegating to `items.len`), and `xs.len = n` sets it (e.g.
|
||||
// `xs.len = 0` to clear the list without freeing its buffer — `cap` and the
|
||||
// backing allocation are untouched, so appends reuse the same storage).
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> i64 {
|
||||
xs : List(i64) = .{};
|
||||
xs.append(10);
|
||||
xs.append(20);
|
||||
xs.append(30);
|
||||
print("len={} cap={}\n", xs.len, xs.cap); // len=3 cap=4
|
||||
|
||||
xs.len = 0; // clear via the #set property
|
||||
print("after clear: len={} cap={}\n", xs.len, xs.cap); // len=0 cap=4
|
||||
|
||||
// The buffer survived the clear — re-append reuses it (cap stays 4).
|
||||
xs.append(99);
|
||||
print("reused: len={} cap={} first={}\n", xs.len, xs.cap, xs.items[0]); // 1 4 99
|
||||
return 0;
|
||||
}
|
||||
1
examples/memory/expected/0841-memory-list-len-set.exit
Normal file
1
examples/memory/expected/0841-memory-list-len-set.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/memory/expected/0841-memory-list-len-set.stderr
Normal file
1
examples/memory/expected/0841-memory-list-len-set.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
3
examples/memory/expected/0841-memory-list-len-set.stdout
Normal file
3
examples/memory/expected/0841-memory-list-len-set.stdout
Normal file
@@ -0,0 +1,3 @@
|
||||
len=3 cap=4
|
||||
after clear: len=0 cap=4
|
||||
reused: len=1 cap=4 first=99
|
||||
63
examples/types/0198-types-set-property-accessor.sx
Normal file
63
examples/types/0198-types-set-property-accessor.sx
Normal file
@@ -0,0 +1,63 @@
|
||||
// `#set` property accessors — the write counterpart of `#get`. A method
|
||||
// `name :: (self: *T, value: V) #set { ... }` is invoked via field-assign
|
||||
// syntax (`obj.name = rhs`) rather than `obj.name(rhs)`. A property may carry
|
||||
// BOTH a `#get` and a `#set` of the same name (reads pick the getter, writes
|
||||
// pick the setter); compound assignment (`+=`) reads via `#get` then writes via
|
||||
// `#set`. Works on plain structs and generic-struct instances, as a multi-assign
|
||||
// target, and the compound form evaluates the receiver exactly once.
|
||||
#import "modules/std.sx";
|
||||
|
||||
// get + set pair on the same name, with a scaling setter so we can see which
|
||||
// path fired.
|
||||
Temp :: struct {
|
||||
raw: i64 = 0;
|
||||
celsius :: (self: *Temp) -> i64 #get => self.raw;
|
||||
celsius :: (self: *Temp, v: i64) #set { self.raw = v; }
|
||||
}
|
||||
|
||||
// Generic instance: setter takes the type parameter as its value type.
|
||||
Box :: struct ($T: Type) {
|
||||
slot: T;
|
||||
val :: (self: *Box(T)) -> T #get => self.slot;
|
||||
val :: (self: *Box(T), v: T) #set { self.slot = v; }
|
||||
}
|
||||
|
||||
main :: () -> i64 {
|
||||
t : Temp = .{};
|
||||
t.celsius = 30; // setter
|
||||
print("celsius={}\n", t.celsius); // 30 (getter)
|
||||
t.celsius += 5; // get-modify-set
|
||||
print("after +=5: {}\n", t.celsius); // 35
|
||||
t.celsius *= 2; // get-modify-set
|
||||
print("after *=2: {}\n", t.celsius); // 70
|
||||
|
||||
b : Box(i64) = .{ slot = 1 };
|
||||
b.val = 99; // setter (value type is T = i64)
|
||||
print("box={}\n", b.val); // 99
|
||||
b.val -= 9;
|
||||
print("box={}\n", b.val); // 90
|
||||
|
||||
// Multi-assign with property targets — a swap proves all RHS values are
|
||||
// evaluated before any setter fires.
|
||||
p : Temp = .{ raw = 1 };
|
||||
q : Temp = .{ raw = 2 };
|
||||
p.celsius, q.celsius = q.celsius, p.celsius;
|
||||
print("swap: p={} q={}\n", p.celsius, q.celsius); // 2 1
|
||||
|
||||
// Compound assign through a property evaluates the receiver EXACTLY ONCE:
|
||||
// a moving receiver reads and writes the SAME element (not two different ones).
|
||||
g_idx = 0;
|
||||
cells[0] = .{ raw = 10 };
|
||||
cells[1] = .{ raw = 20 };
|
||||
next_cell().celsius += 1; // reads & writes cells[0]
|
||||
print("once: c0={} c1={} idx={}\n", cells[0].celsius, cells[1].celsius, g_idx); // 11 20 1
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_idx : i64 = 0;
|
||||
cells : [2]Temp = .[ .{}, .{} ];
|
||||
next_cell :: () -> *Temp {
|
||||
cur := g_idx;
|
||||
g_idx = g_idx + 1;
|
||||
return @cells[cur];
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
celsius=30
|
||||
after +=5: 35
|
||||
after *=2: 70
|
||||
box=99
|
||||
box=90
|
||||
swap: p=2 q=1
|
||||
once: c0=11 c1=20 idx=1
|
||||
Reference in New Issue
Block a user