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:
agra
2026-06-22 17:55:18 +03:00
parent 5cc45a2b38
commit 9523c29173
36 changed files with 526 additions and 19 deletions

View File

@@ -400,7 +400,7 @@ impl Platform for AndroidPlatform {
}
poll_events :: (self: *AndroidPlatform) -> []Event {
self.events.items.len = 0;
self.events.len = 0;
sx_android_drain_touches(self, @self.events);
result : []Event = ---;
result.ptr = self.events.items;

View File

@@ -144,7 +144,7 @@ impl Platform for SdlPlatform {
}
poll_events :: (self: *SdlPlatform) -> []Event {
self.events.items.len = 0;
self.events.len = 0;
sdl_event : SDL_Event = .none;
while SDL_PollEvent(@sdl_event) {
if sdl_event == {

View File

@@ -379,7 +379,7 @@ impl Platform for UIKitPlatform {
result : []Event = ---;
result.ptr = self.events.items;
result.len = self.events.len;
self.events.items.len = 0;
self.events.len = 0;
result
}

View File

@@ -15,6 +15,10 @@ List :: struct ($T: Type) {
// No-paren read accessor: `xs.len` → the live element count.
len :: (self: *List(T)) -> i64 #get => self.items.len;
// Write accessor: `xs.len = n` sets the live count (e.g. `xs.len = 0` to
// clear without freeing). Mirrors the `#get` above; the buffer / `cap` are
// untouched, so `n` must be `<= cap`.
len :: (self: *List(T), v: i64) #set { self.items.len = v; }
append :: (list: *List(T), item: T, alloc: Allocator = context.allocator) {
if list.items.len >= list.cap {

View File

@@ -647,7 +647,7 @@ remove_timer :: (self: *Scheduler, idx: i64) {
self.timers.items[i] = self.timers.items[i + 1];
i = i + 1;
}
self.timers.items.len = self.timers.items.len - 1;
self.timers.len = self.timers.len - 1;
}
// Remove a pending sleep timer referencing fiber `f`, if any. A fiber has at
@@ -676,7 +676,7 @@ remove_io_waiter :: (self: *Scheduler, idx: i64) {
self.io_waiters.items[i] = self.io_waiters.items[i + 1];
i = i + 1;
}
self.io_waiters.items.len = self.io_waiters.items.len - 1;
self.io_waiters.len = self.io_waiters.len - 1;
}
// Remove a pending fd-waiter referencing fiber `f`, if any. A fiber has at most

View File

@@ -575,7 +575,7 @@ GlyphCache :: struct {
return; // shaped_buf already has the result
}
self.shaped_buf.items.len = 0;
self.shaped_buf.len = 0;
if text.len == 0 { return; }
if is_ascii(text) {

View File

@@ -141,7 +141,7 @@ UIPipeline :: struct {
// Reset render_tree nodes (backing is stale after arena reset)
self.render_tree.nodes.items = null;
self.render_tree.nodes.items.len = 0;
self.render_tree.nodes.len = 0;
self.render_tree.nodes.cap = 0;
push Context.{ allocator = xx build_arena, data = context.data } {

View File

@@ -47,7 +47,7 @@ RenderTree :: struct {
}
clear :: (self: *RenderTree) {
self.nodes.items.len = 0;
self.nodes.len = 0;
self.generation += 1;
}