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:
@@ -2322,6 +2322,7 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
|
||||
func.is_variadic = is_variadic;
|
||||
func.has_implicit_ctx = wants_ctx;
|
||||
func.is_naked = (fd.abi == .naked);
|
||||
func.is_get = fd.is_get;
|
||||
self.extern_name_map.put(name, c_name) catch {};
|
||||
self.fn_decl_fids.put(fd, fid) catch {};
|
||||
return;
|
||||
@@ -2336,6 +2337,7 @@ pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8)
|
||||
func.is_variadic = is_variadic;
|
||||
func.has_implicit_ctx = wants_ctx;
|
||||
func.is_naked = (fd.abi == .naked);
|
||||
func.is_get = fd.is_get;
|
||||
if (weldedCompilerFn(self, fd, name)) func.compiler_welded = true;
|
||||
// A BODIED `abi(.compiler)` function is a user compiler-domain function (e.g. a
|
||||
// post-link callback): the VM runs its sx body, but it NEVER runs in the binary
|
||||
|
||||
@@ -637,6 +637,30 @@ pub fn lowerFieldAccess(self: *Lowering, fa: *const ast.FieldAccess, span: ast.S
|
||||
return self.lowerObjcDefinedStateFieldRead(fa.object, info);
|
||||
}
|
||||
|
||||
// `#get` property accessor: `obj.field` where `field` is a `#get` method
|
||||
// dispatches as a no-paren method call (`obj.field()`). Detected via type
|
||||
// info only (no lowering) so the receiver is not evaluated twice — the
|
||||
// synthesized call re-lowers `fa.object` and handles the receiver
|
||||
// address-of + any generic binding itself.
|
||||
{
|
||||
var recv_ty = self.inferExprType(fa.object);
|
||||
if (!recv_ty.isBuiltin()) {
|
||||
const di = self.module.types.get(recv_ty);
|
||||
if (di == .pointer) recv_ty = di.pointer.pointee;
|
||||
}
|
||||
if (self.getAccessorFor(recv_ty, fa.field) != null) {
|
||||
// For an explicit-deref receiver `(*p).getter`, dispatch on the
|
||||
// inner pointer `p` (`p.getter`, auto-deref) — semantically identical
|
||||
// and it takes the working receiver path (the synthesized call on a
|
||||
// `.deref_expr` receiver otherwise mis-lowers the `*self` address).
|
||||
var recv_fa = fa.*;
|
||||
if (fa.object.data == .deref_expr) recv_fa.object = fa.object.data.deref_expr.operand;
|
||||
const callee_node = Node{ .data = .{ .field_access = recv_fa }, .span = span };
|
||||
const syn_call = ast.Call{ .callee = @constCast(&callee_node), .args = &.{} };
|
||||
return self.lowerCall(&syn_call);
|
||||
}
|
||||
}
|
||||
|
||||
var obj = self.lowerExpr(fa.object);
|
||||
var obj_ty = self.inferExprType(fa.object);
|
||||
|
||||
@@ -825,6 +849,38 @@ pub fn vectorLaneIndex(field: []const u8) ?u32 {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// A `#get` property accessor for `obj_ty.field`, or null. A `#get` method is a
|
||||
/// normal method (registered `Type.method`) marked `is_get`; it is reachable via
|
||||
/// no-paren field syntax. Handles a generic-struct instance (`List(i64).len`)
|
||||
/// and a plain struct (`Foo.bar`). `ty` must be the dereferenced (non-pointer)
|
||||
/// receiver type.
|
||||
pub fn getAccessorFor(self: *Lowering, ty: TypeId, field: []const u8) ?*const ast.FnDecl {
|
||||
if (ty.isBuiltin()) return null;
|
||||
// A REAL field of this name wins over a same-name `#get` (a getter must not
|
||||
// shadow stored data on the read path). If the struct genuinely declares the
|
||||
// field, this is not a property access.
|
||||
const field_id = self.module.types.internString(field);
|
||||
for (self.getStructFields(ty)) |f| {
|
||||
if (f.name == field_id) return null;
|
||||
}
|
||||
// Generic instance: genericInstanceMethod is keyed by the instance name
|
||||
// (e.g. "List(i64)"), which is what formatTypeName produces.
|
||||
const tn = self.formatTypeName(ty);
|
||||
if (self.genericInstanceMethod(tn, field)) |m| {
|
||||
return if (m.fd.is_get) m.fd else null;
|
||||
}
|
||||
// Plain struct: methods are registered "StructName.method" in fn_ast_map.
|
||||
const info = self.module.types.get(ty);
|
||||
if (info == .@"struct") {
|
||||
const sname = self.module.types.getString(info.@"struct".name);
|
||||
const q = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sname, field }) catch return null;
|
||||
if (self.program_index.fn_ast_map.get(q)) |fd| {
|
||||
return if (fd.is_get) fd else null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn lowerFieldAccessOnType(self: *Lowering, obj: Ref, obj_ty: TypeId, field: []const u8, span: ast.Span) Ref {
|
||||
const field_name_id = self.module.types.internString(field);
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ pub fn monomorphizeFunction(self: *Lowering, fd: *const ast.FnDecl, mangled_name
|
||||
_ = func_id;
|
||||
self.builder.currentFunc().has_implicit_ctx = wants_ctx;
|
||||
self.builder.currentFunc().is_naked = (fd.abi == .naked);
|
||||
self.builder.currentFunc().is_get = fd.is_get;
|
||||
|
||||
// Create entry block
|
||||
const entry_name = self.module.types.internString("entry");
|
||||
|
||||
@@ -950,6 +950,7 @@ pub fn monomorphizePackFn(
|
||||
_ = self.builder.beginFunction(name_id, params.items, ret_ty);
|
||||
self.builder.currentFunc().has_implicit_ctx = wants_ctx;
|
||||
self.builder.currentFunc().is_naked = (fd.abi == .naked);
|
||||
self.builder.currentFunc().is_get = fd.is_get;
|
||||
|
||||
const entry_name = self.module.types.internString("entry");
|
||||
const entry = self.builder.appendBlock(entry_name, &.{});
|
||||
|
||||
Reference in New Issue
Block a user