lang: opt-in UFCS — ufcs-marked fns + alias dot-dispatch, generic binding via receiver; one binding builder for plan-side generic returns

This commit is contained in:
agra
2026-06-11 17:04:51 +03:00
parent 84e0fb0752
commit a47ea1416e
27 changed files with 316 additions and 137 deletions

View File

@@ -2279,34 +2279,50 @@ print("hello")
### UFCS (Uniform Function Call Syntax)
```sx
object.func(args) // equivalent to func(object, args)
object.func(args) // equivalent to func(object, args) — for OPT-IN functions
```
When `object.func(args)` is encountered and `func` is not a field of `object`'s type, the compiler rewrites the call to `func(object, args)`. This enables method-like syntax without dedicated method declarations.
Free-function dot-calls are **opt-in**: a plain function never dispatches
via dot. The `ufcs` keyword opts a function in, with two spellings —
marking the function itself, or declaring a (renaming) alias:
```sx
Point :: struct { x: s32; y: s32; }
point_sum :: (p: Point) -> s32 { p.x + p.y; }
create :: (x: s32) -> void {} // plain — NOT dot-callable
create2 :: ufcs (x: s32) -> void {} // ufcs-marked — dot-callable
create3 :: ufcs create; // ufcs alias — dot-callable
p := Point.{3, 4};
print("{}\n", p.point_sum()); // calls point_sum(p) → 7
f : s32 = 4;
f.create(); // error: 'create' is not a ufcs function (help: call it
// directly, pipe it, or declare it `create :: ufcs (...)`)
f.create2(); // works — calls create2(f)
f.create3(); // works — calls create(f) through the alias
create2(f); // a ufcs fn is still an ordinary fn: direct calls work
f |> create(); // the pipe works on ANY fn (parse-time desugar, no opt-in)
```
UFCS works with pointer receivers (auto-deref applies). Generic struct
*methods* dispatch via dot; a generic **free function** (any `$T` in its
signature) is NOT dot-rewritten — call it directly or fluently via the
pipe operator, which desugars at parse time to the direct call:
When `object.func(args)` names an opted-in function and `func` is not a
field or method of `object`'s type, the compiler rewrites the call to
`func(object, args)`. Fields and methods take priority over ufcs
functions; a protocol-typed receiver dispatches its own methods first and
falls through to ufcs functions for non-members
(`context.allocator.create(Session)` — `create` is a ufcs fn taking the
protocol value as its first param).
UFCS works with pointer receivers (auto-deref, and auto address-of when
the first param is `*T` and the receiver is a value) and with **generic**
functions — the receiver participates in `$T` binding and the call
monomorphizes exactly like the direct spelling:
```sx
first_of :: (xs: []$T) -> T { xs[0] }
first_of :: ufcs (xs: []$T) -> T { xs[0] }
xs.first_of(); // dot — binds $T from the receiver
first_of(xs); // direct
xs |> first_of(); // fluent — desugars to first_of(xs)
xs |> first_of(); // pipe — desugars to first_of(xs)
```
If the field name exists as both a struct field and a free function, the struct field takes priority.
#### UFCS Aliases
The `ufcs` keyword creates a name alias for a function, decoupling the method name from the function name:
The alias form decouples the method name from the function name
useful when the bare name reads poorly in dot position:
```sx
arena_alloc :: (arena: *Arena, size: s64) -> *void { ... }
alloc :: ufcs arena_alloc;