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:
agra
2026-06-22 18:55:41 +03:00
parent 1b0c857b91
commit ff9e448f8c
10 changed files with 214 additions and 4 deletions

View File

@@ -315,6 +315,13 @@ pub fn lowerVarDecl(self: *Lowering, vd: *const ast.VarDecl) void {
if (!ty.isBuiltin()) {
const ty_info = self.module.types.get(ty);
if (ty_info == .optional and val.data != .null_literal and self.builder.getRefType(ref) != ty) {
// Coerce to the optional's CHILD first (e.g. an array value
// into a `?[]T` promotes array→slice), THEN wrap — wrapping
// the raw value would store e.g. array bits into the slice
// payload and corrupt `.len`/`.ptr`.
const child = ty_info.optional.child;
const rt = self.builder.getRefType(ref);
if (rt != child and rt != .void and child != .void) ref = self.coerceToType(ref, rt, child);
ref = self.builder.optionalWrap(ref, ty);
} else if (ty_info == .slice) {
// Array → slice promotion: if value is an array, convert to slice