|
|
|
|
@@ -95,6 +95,34 @@ pub fn lowerStructLiteral(self: *Lowering, sl: *const ast.StructLiteral, span: a
|
|
|
|
|
return self.lowerUnionLiteral(sl, ty, span);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A bare struct literal against an optional target `?T` builds the INNER
|
|
|
|
|
// `T` and wraps it once. Without this `ty` is the optional itself, so the
|
|
|
|
|
// literal is lowered into the optional's `{payload, has_value}` layout and
|
|
|
|
|
// then re-wrapped — corrupting the value (a `?T` arg silently reads as
|
|
|
|
|
// null) or failing LLVM verification on the double wrap (issue 0160).
|
|
|
|
|
// Only the bare `.{ ... }` form reaches here with an optional `ty` (a named
|
|
|
|
|
// / generic literal resolves `ty` from its own type, never the target).
|
|
|
|
|
if (!ty.isBuiltin() and self.module.types.get(ty) == .optional) {
|
|
|
|
|
const child = self.module.types.get(ty).optional.child;
|
|
|
|
|
// Build the inner `T` (targeting it so nested literals resolve), then
|
|
|
|
|
// wrap to `?T`. Building into `ty` (the optional) directly would fill
|
|
|
|
|
// its {payload, has_value} layout and corrupt the value / fail LLVM
|
|
|
|
|
// verification. Wrapping the raw struct_init SSA aggregate ALSO mislays
|
|
|
|
|
// a multi-field payload, so round-trip through memory first — the wrap
|
|
|
|
|
// then sees a loaded value, the same shape the working `T -> ?T` value
|
|
|
|
|
// coercion wraps. Returning a fully-built `?T` makes EVERY caller
|
|
|
|
|
// context correct, including array/struct-literal element slots that
|
|
|
|
|
// don't re-coerce (issue 0160).
|
|
|
|
|
const saved_tt = self.target_type;
|
|
|
|
|
self.target_type = child;
|
|
|
|
|
const inner = self.lowerStructLiteral(sl, span);
|
|
|
|
|
self.target_type = saved_tt;
|
|
|
|
|
const slot = self.builder.alloca(child);
|
|
|
|
|
self.builder.store(slot, inner);
|
|
|
|
|
const reloaded = self.builder.load(slot, child);
|
|
|
|
|
return self.coerceToType(reloaded, child, ty);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get struct field types for coercion and ordering
|
|
|
|
|
const struct_fields = self.getStructFields(ty);
|
|
|
|
|
|
|
|
|
|
@@ -802,8 +830,37 @@ pub fn lowerOptionalChain(self: *Lowering, obj: Ref, fa: *const ast.FieldAccess,
|
|
|
|
|
break :blk if (info == .optional) info.optional.child else obj_ty;
|
|
|
|
|
} else obj_ty;
|
|
|
|
|
|
|
|
|
|
// Get the field type on the inner type
|
|
|
|
|
const field_ty = self.resolveFieldType(inner_ty, fa.field);
|
|
|
|
|
// `#get` accessor through `?.`: if the unwrapped (and pointer-deref'd)
|
|
|
|
|
// receiver has a getter for this field, the some-branch dispatches the
|
|
|
|
|
// getter instead of a struct-field read. A synthetic receiver local (typed
|
|
|
|
|
// `inner_ty`) lets the existing getter intercept in `lowerFieldAccess` do
|
|
|
|
|
// the deref / address-of; we bind it type-only here for the return-type
|
|
|
|
|
// query, then fill its ref in the some-branch (issue 0160).
|
|
|
|
|
var deref_inner = inner_ty;
|
|
|
|
|
if (!deref_inner.isBuiltin() and self.module.types.get(deref_inner) == .pointer)
|
|
|
|
|
deref_inner = self.module.types.get(deref_inner).pointer.pointee;
|
|
|
|
|
const getter_recv: ?[]const u8 = if (self.scope != null and self.getAccessorFor(deref_inner, fa.field) != null) blk: {
|
|
|
|
|
var buf: [40]u8 = undefined;
|
|
|
|
|
const nm = std.fmt.bufPrint(&buf, "$oc_recv_{d}", .{self.block_counter}) catch "$oc_recv";
|
|
|
|
|
self.block_counter += 1;
|
|
|
|
|
const owned = self.alloc.dupe(u8, nm) catch break :blk null;
|
|
|
|
|
// Type-only binding for the return-type query: type it as a POINTER to
|
|
|
|
|
// the struct so inference routes through the (working) pointer-deref
|
|
|
|
|
// getter path for both `?T` and `?*T`. The some-branch re-binds it to
|
|
|
|
|
// the actual unwrapped receiver before lowering.
|
|
|
|
|
self.scope.?.put(owned, .{ .ref = Ref.none, .ty = self.module.types.ptrTo(deref_inner), .is_alloca = false });
|
|
|
|
|
break :blk owned;
|
|
|
|
|
} else null;
|
|
|
|
|
const read_node: ?*Node = if (getter_recv) |nm| blk: {
|
|
|
|
|
const id = self.alloc.create(Node) catch break :blk null;
|
|
|
|
|
id.* = .{ .span = span, .data = .{ .identifier = .{ .name = nm } } };
|
|
|
|
|
const rn = self.alloc.create(Node) catch break :blk null;
|
|
|
|
|
rn.* = .{ .span = span, .data = .{ .field_access = .{ .object = id, .field = fa.field } } };
|
|
|
|
|
break :blk rn;
|
|
|
|
|
} else null;
|
|
|
|
|
|
|
|
|
|
// Get the field type on the inner type (the getter's return type, if any).
|
|
|
|
|
const field_ty = if (read_node) |rn| self.inferExprType(rn) else self.resolveFieldType(inner_ty, fa.field);
|
|
|
|
|
// If field is already optional, flatten (don't double-wrap)
|
|
|
|
|
const field_already_optional = if (!field_ty.isBuiltin()) self.module.types.get(field_ty) == .optional else false;
|
|
|
|
|
const result_ty = if (field_already_optional) field_ty else self.module.types.optionalOf(field_ty);
|
|
|
|
|
@@ -821,7 +878,24 @@ pub fn lowerOptionalChain(self: *Lowering, obj: Ref, fa: *const ast.FieldAccess,
|
|
|
|
|
// Some: unwrap, access field (already ?FieldType if flattened, else wrap)
|
|
|
|
|
self.builder.switchToBlock(some_bb);
|
|
|
|
|
const unwrapped = self.builder.emit(.{ .optional_unwrap = .{ .operand = obj } }, inner_ty);
|
|
|
|
|
const field_val = self.lowerFieldAccessOnType(unwrapped, inner_ty, fa.field, span);
|
|
|
|
|
const field_val = if (read_node) |rn| blk: {
|
|
|
|
|
// Re-bind the synthetic receiver to the unwrapped value, then dispatch
|
|
|
|
|
// the getter through the normal field-access intercept. A `?*T` unwraps
|
|
|
|
|
// to the pointer receiver directly; a `?T` unwraps to a value that the
|
|
|
|
|
// getter (`self: *T`) needs to address, so materialize it into an alloca
|
|
|
|
|
// and bind it like an ordinary `T` local (is_alloca).
|
|
|
|
|
if (!inner_ty.isBuiltin() and self.module.types.get(inner_ty) == .pointer) {
|
|
|
|
|
self.scope.?.put(getter_recv.?, .{ .ref = unwrapped, .ty = inner_ty, .is_alloca = false });
|
|
|
|
|
} else {
|
|
|
|
|
// Materialize the value and bind the alloca POINTER as a `*T` value
|
|
|
|
|
// (not an is_alloca `T`), so the receiver path is identical to the
|
|
|
|
|
// `?*T` case — a plain pointer receiver the getter intercept derefs.
|
|
|
|
|
const slot = self.builder.alloca(inner_ty);
|
|
|
|
|
self.builder.store(slot, unwrapped);
|
|
|
|
|
self.scope.?.put(getter_recv.?, .{ .ref = slot, .ty = self.module.types.ptrTo(inner_ty), .is_alloca = false });
|
|
|
|
|
}
|
|
|
|
|
break :blk self.lowerExpr(rn);
|
|
|
|
|
} else self.lowerFieldAccessOnType(unwrapped, inner_ty, fa.field, span);
|
|
|
|
|
const some_result = if (field_already_optional) field_val else self.builder.emit(.{ .optional_wrap = .{ .operand = field_val } }, result_ty);
|
|
|
|
|
self.builder.br(merge_bb, &.{some_result});
|
|
|
|
|
|
|
|
|
|
@@ -886,6 +960,29 @@ pub fn getAccessorFor(self: *Lowering, ty: TypeId, field: []const u8) ?*const as
|
|
|
|
|
/// effective `field$set` name (so a same-name `#get` keeps the plain `field`),
|
|
|
|
|
/// and a REAL field of the same name wins over it (parallels the `#get` rule).
|
|
|
|
|
/// `ty` must be the dereferenced (non-pointer) receiver type.
|
|
|
|
|
/// The return type of a `#get` accessor named `field` on `deref_ty` (a
|
|
|
|
|
/// dereferenced struct type), or null when there is no such getter (or no scope
|
|
|
|
|
/// to resolve through). Resolves the type the SAME way a real read does — via a
|
|
|
|
|
/// synthetic `*deref_ty` receiver local routed through inference — so a generic
|
|
|
|
|
/// instance getter (`List(T).len`) binds its type parameter exactly as the call
|
|
|
|
|
/// path would. Shared by `lowerOptionalChain` and the optional-chain inference
|
|
|
|
|
/// in `expr_typer`, so `obj?.getter` types identically to how it lowers.
|
|
|
|
|
pub fn getterReturnTypeOnDeref(self: *Lowering, deref_ty: TypeId, field: []const u8) ?TypeId {
|
|
|
|
|
if (deref_ty.isBuiltin()) return null;
|
|
|
|
|
if (self.getAccessorFor(deref_ty, field) == null) return null;
|
|
|
|
|
const s = self.scope orelse return null;
|
|
|
|
|
var buf: [40]u8 = undefined;
|
|
|
|
|
const nm = std.fmt.bufPrint(&buf, "$oc_ty_{d}", .{self.block_counter}) catch "$oc_ty";
|
|
|
|
|
self.block_counter += 1;
|
|
|
|
|
const owned = self.alloc.dupe(u8, nm) catch return null;
|
|
|
|
|
s.put(owned, .{ .ref = Ref.none, .ty = self.module.types.ptrTo(deref_ty), .is_alloca = false });
|
|
|
|
|
const id = self.alloc.create(Node) catch return null;
|
|
|
|
|
id.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = owned } } };
|
|
|
|
|
const rn = self.alloc.create(Node) catch return null;
|
|
|
|
|
rn.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .field_access = .{ .object = id, .field = field } } };
|
|
|
|
|
return self.inferExprType(rn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn getSetterFor(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 `#set` (a setter must not
|
|
|
|
|
|