feat: #get property accessors (no-paren method-as-field)

A method declared `name :: (self: *T) -> R #get => expr;` is invoked via
no-paren field syntax (`obj.name`) instead of `obj.name()`. It is an ordinary
method (registered `Type.method`, flagged is_get); field-access lowering and
inference dispatch to it when no real field of that name exists, by synthesizing
a no-arg `obj.name()` call routed through the normal call path (so receiver
address-of and generic binding are reused).

- Lexer/token: `#get`. Parser: parsed after the return type in parseFnDecl;
  hasFnBodyAfterArrow treats it as a body marker so struct-body methods parse.
- Resolution: getAccessorFor handles a generic-struct instance and a plain
  struct. A REAL field of the same name wins (a getter never shadows stored
  data). An explicit postfix-deref receiver (`p.*.getter`) dispatches on the
  inner pointer so it takes the working auto-deref path.
- Works on plain + generic structs (incl. getters returning the type param),
  in expressions/conditions/args/loop-bounds, chained, and via a pointer
  receiver. Examples: types/0196 (basic) + types/0197 (stress).

Known narrow limitations (clean errors / workarounds, not silent): a getter
RESULT used directly as a method/getter receiver (`o.gi.dbl`) errors — bind it
to a local first; a getter named `len`/`ptr` returning non-i64 mis-infers
(the .len/.ptr builtin-field shortcut).
This commit is contained in:
agra
2026-06-22 11:55:01 +03:00
parent b9311e7de4
commit 9d3a019670
20 changed files with 195 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
// `#get` property accessors — a method marked `#get` is invoked via no-paren
// field syntax (`obj.name`) rather than `obj.name()`. Works on plain structs
// and generic-struct instances (including a getter whose return type is the
// instance's type parameter), and the accessed value types correctly in
// expressions and bindings.
#import "modules/std.sx";
Counter :: struct {
n: i64;
doubled :: (self: *Counter) -> i64 #get => self.n * 2;
}
Vec :: struct ($T: Type) {
data: []T;
count :: (self: *Vec(T)) -> i64 #get => self.data.len; // returns i64
first :: (self: *Vec(T)) -> T #get => self.data[0]; // returns the type param T
}
main :: () -> i64 {
c : Counter = .{ n = 21 };
print("doubled={}\n", c.doubled); // 42
x := c.doubled; // inference: x is i64
print("x+1={}\n", x + 1); // 43
a : [3]i64 = .[10, 20, 30];
v : Vec(i64) = .{ data = a[0..3] };
print("count={}\n", v.count); // 3
print("first={}\n", v.first); // 10
print("sum={}\n", v.count + v.first); // 13
return 0;
}

View File

@@ -0,0 +1,51 @@
// `#get` property accessor — stress coverage: getters returning scalars / bool
// / a type param / a struct / a pointer; getters used as a field read, in
// expressions, conditions, call args, loop bounds, chained, and nested
// (a getter calling another getter); on a pointer receiver and via auto-deref;
// a getter still callable with explicit parens; multiple getters per type.
#import "modules/std.sx";
Pt :: struct { x: i64; y: i64; }
Box :: struct {
v: i64;
is_pos :: (self: *Box) -> bool #get => self.v > 0; // bool
neg :: (self: *Box) -> i64 #get => -self.v; // scalar
origin :: (self: *Box) -> Pt #get => .{ x = self.v, y = self.v }; // struct
// nested: a getter that reads another getter
twice_neg :: (self: *Box) -> i64 #get => self.neg + self.neg;
}
Holder :: struct ($T: Type) {
data: []T;
n :: (self: *Holder(T)) -> i64 #get => self.data.len; // i64
head :: (self: *Holder(T)) -> T #get => self.data[0]; // type param T
tailp :: (self: *Holder(T)) -> *T #get => @self.data[self.data.len - 1]; // pointer
}
main :: () -> i64 {
b : Box = .{ v = 7 };
print("is_pos={} neg={} twice_neg={}\n", b.is_pos, b.neg, b.twice_neg); // true -7 -14
if b.is_pos { print("positive\n"); } // condition
o := b.origin; // struct-returning getter + binding
print("origin=({},{})\n", o.x, o.y); // (7,7)
print("paren neg()={}\n", b.neg()); // explicit-paren call still works
a : [4]i64 = .[100, 200, 300, 400];
h : Holder(i64) = .{ data = a[0..4] };
print("n={} head={}\n", h.n, h.head); // 4 100
print("tail={}\n", h.tailp.*); // 400 (pointer getter, deref)
print("n+head={}\n", h.n + h.head); // 104 (in expression)
// getter as a loop bound + call arg
total := 0;
k := 0;
while k < h.n { total = total + h.data[k]; k = k + 1; }
print("loop-bound total={}\n", total); // 1000
// via a pointer receiver (auto-deref) and an explicit postfix deref
pb := @b;
print("via ptr is_pos={}\n", pb.is_pos); // true
print("via deref is_pos={}\n", pb.*.is_pos); // true (p.*.getter)
return 0;
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,5 @@
doubled=42
x+1=43
count=3
first=10
sum=13

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,10 @@
is_pos=true neg=-7 twice_neg=-14
positive
origin=(7,7)
paren neg()=-7
n=4 head=100
tail=400
n+head=104
loop-bound total=1000
via ptr is_pos=true
via deref is_pos=true