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).
44 lines
2.0 KiB
Markdown
44 lines
2.0 KiB
Markdown
# 0161 — struct literal against a non-aggregate (scalar) type crashes instead of diagnosing
|
|
|
|
## Symptom
|
|
|
|
A struct literal `.{ field = ... }` whose resolved target type is a scalar (or
|
|
any non-struct) reaches LLVM emission and fails verification, instead of emitting
|
|
a clean "struct literal against non-struct type" diagnostic.
|
|
|
|
- Observed: `LLVM verification failed: Invalid InsertValueInst operands! %si = insertvalue i64 undef, i64 1, 0` (exit 1).
|
|
- Expected: a diagnostic like "cannot build a struct literal for non-struct type 'i64'".
|
|
|
|
`.{}` (empty) against a scalar is worse — it silently produces garbage with no
|
|
diagnostic.
|
|
|
|
This surfaced while reviewing issue 0160: `?i64 = .{...}` routes through the
|
|
struct-literal→optional path (which recurses with the child type `i64` as
|
|
target) into this same crash. But it is NOT optional-specific — a plain
|
|
`i64 = .{...}` crashes identically, so the root cause is the general
|
|
struct-literal path, not the 0160 optional handling.
|
|
|
|
## Reproduction
|
|
|
|
```sx
|
|
#import "modules/std.sx";
|
|
main :: () {
|
|
x : i64 = .{ a = 1 }; // struct literal targeting a scalar
|
|
print("{}\n", x); // actual: LLVM verification failure
|
|
}
|
|
```
|
|
Also: `y : i64 = .{}` → silent garbage; `o : ?i64 = .{ a = 1 }` → same crash.
|
|
|
|
## Investigation prompt
|
|
|
|
`src/ir/lower/expr.zig` `lowerStructLiteral`: after the resolved literal type
|
|
`ty` is computed (and the optional/union special-cases), the named/positional
|
|
field path calls `getStructFields(ty)` and emits `structInit`/`insertvalue`
|
|
without first checking that `ty` is actually a struct. Add an early guard: if
|
|
`ty.isBuiltin()` or `module.types.get(ty)` is not `.@"struct"` (after the
|
|
existing tagged-union / union / optional intercepts), emit a diagnostic via
|
|
`self.diagnostics.addFmt(.err, span, "...", .{...})` and return a placeholder,
|
|
rather than building `insertvalue` against a scalar LLVM type. Verify with the
|
|
repro (expect a clean error, exit 1, no LLVM panic). Add
|
|
`examples/diagnostics/11xx-...` for the negative case.
|