fix: optional-chain getter/field correctness from 0160 adversarial review
Five adversarial reviews of the issue-0160 fix surfaced three more bugs in the touched optional-chain / optional-coercion code; all fixed here: 1. A COLD generic-instance getter through `?.` (`?*Vec(i64)` `.getter`, never called directly first) panicked with "unresolved type reached LLVM emission": a cold instance method is absent from resolveFuncByName, so the getter's return type resolved to .unresolved → a ?unresolved merge type. lowerOptionalChain and getterReturnTypeOnDeref now warm the monomorph (ensureGenericInstanceMethodLowered) before querying its return type. (The 0907 test passed only by luck — List(i64) is warmed by stdlib use; 0907 now also exercises a cold user generic.) 2. A real-field read through a `?*T` chain (`op?.field`, op: ?*T) reinterpreted the pointer bits as the field (silent garbage) — the some-branch real-field path didn't load through the pointer. It now derefs `?*T` before the field access. (Pre-existing — the else-branch predates 0160 — but it's the same function and a silent miscompile, so fixed here.) 3. `?[]T = array` skipped the array→slice promotion (corrupt .len/.ptr): the lowerVarDecl optional arm wrapped the raw array. It now coerces the value to the optional's child type (array→slice) before wrapping. Regression examples 0906/0907 extended to cover all three. Distinct PRE-EXISTING bugs the reviews surfaced in untouched subsystems are filed as issues 0161 (struct-literal vs scalar), 0162 (#run returning an optional aggregate), 0163 (untagged-union payload-binding match).
This commit is contained in:
@@ -859,6 +859,16 @@ pub fn lowerOptionalChain(self: *Lowering, obj: Ref, fa: *const ast.FieldAccess,
|
||||
break :blk rn;
|
||||
} else null;
|
||||
|
||||
// Warm a generic-instance getter's monomorph BEFORE typing the chain: a
|
||||
// cold instance method is absent from `resolveFuncByName`, so `resultType`
|
||||
// would resolve the getter to `.unresolved` → a `?unresolved` merge type
|
||||
// that panics at LLVM emission. Lowering it now binds its type parameter so
|
||||
// both the type query and the some-branch call see the concrete signature.
|
||||
if (getter_recv != null) {
|
||||
const tn = self.formatTypeName(deref_inner);
|
||||
if (self.genericInstanceMethod(tn, fa.field)) |gm| _ = self.ensureGenericInstanceMethodLowered(gm);
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -895,7 +905,21 @@ pub fn lowerOptionalChain(self: *Lowering, obj: Ref, fa: *const ast.FieldAccess,
|
||||
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);
|
||||
} else blk: {
|
||||
// Real-field read. For a `?*T` the unwrapped value is the POINTER, so
|
||||
// load through it before the struct-field access — `lowerFieldAccessOnType`
|
||||
// does not auto-deref, and a `structGet` on the raw pointer would read
|
||||
// the pointer bits as the field (silent garbage). A `?T` value optional
|
||||
// accesses the unwrapped value directly.
|
||||
var fobj = unwrapped;
|
||||
var fty = inner_ty;
|
||||
if (!fty.isBuiltin() and self.module.types.get(fty) == .pointer) {
|
||||
const pointee = self.module.types.get(fty).pointer.pointee;
|
||||
fobj = self.builder.load(unwrapped, pointee);
|
||||
fty = pointee;
|
||||
}
|
||||
break :blk self.lowerFieldAccessOnType(fobj, fty, 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});
|
||||
|
||||
@@ -971,6 +995,10 @@ pub fn getterReturnTypeOnDeref(self: *Lowering, deref_ty: TypeId, field: []const
|
||||
if (deref_ty.isBuiltin()) return null;
|
||||
if (self.getAccessorFor(deref_ty, field) == null) return null;
|
||||
const s = self.scope orelse return null;
|
||||
// Warm a generic-instance getter's monomorph so its return type resolves
|
||||
// (cold instance methods are absent from `resolveFuncByName` → `.unresolved`).
|
||||
if (self.genericInstanceMethod(self.formatTypeName(deref_ty), field)) |gm|
|
||||
_ = self.ensureGenericInstanceMethodLowered(gm);
|
||||
var buf: [40]u8 = undefined;
|
||||
const nm = std.fmt.bufPrint(&buf, "$oc_ty_{d}", .{self.block_counter}) catch "$oc_ty";
|
||||
self.block_counter += 1;
|
||||
|
||||
Reference in New Issue
Block a user