test: remove resolved/fixed issue writeups
Delete the issues/*.md whose writeup carries a RESOLVED or FIXED banner; only the open issues (0030, 0148) remain.
This commit is contained in:
@@ -1,120 +0,0 @@
|
||||
# 0097 — value-failable returning an ENUM corrupts the error slot on the success path
|
||||
|
||||
**RESOLVED.** Root cause was **not** the field-offset/width miscalculation originally
|
||||
hypothesized — `tuple_init` / `tuple_get` and the backend struct layout were correct. The real
|
||||
cause was upstream in `lowerReturn` (`src/ir/lower.zig`): when lowering the returned expression of
|
||||
a value-carrying failable `-> (T..., !)`, `target_type` was set to the **full failable tuple**
|
||||
`(Color, !E)` instead of the success **value** type `Color`. A bare enum literal `.red` resolves
|
||||
its variant tag against `target_type` (`lowerEnumLiteral` → `resolveVariantValue`); against a tuple
|
||||
type there is no matching variant, so it returned the silent `0` default AND stamped the result
|
||||
with the tuple type. `lowerFailableSuccessReturn` then saw `val_ty == ret_ty` and took the
|
||||
**forwarding** branch, returning the half-built aggregate `{ value, undef }` as-is — the appended
|
||||
`constInt(0, err_ty)` was never inserted, leaving the error slot `undef` (read back as garbage
|
||||
nonzero) on the success path.
|
||||
|
||||
**Fix:** in `lowerReturn`, choose the `target_type` for the returned expression via
|
||||
`failableReturnTarget(ret_ty, value_node)`: for a value-carrying failable a **bare** returned
|
||||
value resolves against `failableSuccessType(ret_ty)` (the value type / value-tuple) so an enum
|
||||
literal gets its real ordinal and the success-return path appends the `0` error slot; an
|
||||
**explicit full failable tuple** literal (`return (v..., e)`, arity == full-tuple field count)
|
||||
keeps the full-tuple target so its trailing error element resolves against the error set and is
|
||||
forwarded as-is. The i32 case was already correct because integer literals don't resolve variants
|
||||
against `target_type`.
|
||||
|
||||
Two follow-up defects from the first cut of this fix were corrected (attempt-2 review):
|
||||
|
||||
- **F1 — explicit full tuple return panicked.** Narrowing the target to the value type for *all*
|
||||
value-failables broke `return (.blue, error.Nope)`: the trailing error element no longer
|
||||
resolved against the error set, leaving an `.unresolved` tuple field that tripped the
|
||||
"unresolved type reached LLVM emission" panic in `src/backend/llvm/types.zig`. The
|
||||
arity-aware `failableReturnTarget` keeps the full-tuple target for the explicit form, so it
|
||||
lowers and forwards as before.
|
||||
- **F2 — comptime-param inline return still corrupted.** A `-> (Enum, !E)` body with a comptime
|
||||
parameter is inlined (`lowerComptimeCall`), so its success `return .red` took the
|
||||
inline-return path (`if (self.inline_return_target)`), which the first cut skipped — it stored
|
||||
`{value, undef}` (error slot `undef`) into the inline slot. That path now applies the same
|
||||
target narrowing and routes a value-carrying failable through `lowerFailableSuccessReturn`
|
||||
(whose `emitTupleRet` stores `{value, 0}` into the inline slot + branches), so the success
|
||||
error slot is `0` there too.
|
||||
|
||||
**Regression:** `examples/1055-errors-enum-value-failable-error-slot.sx` (bare-enum success slot)
|
||||
and `examples/1056-errors-enum-value-failable-tuple-and-comptime.sx` (F1 explicit-tuple error +
|
||||
bare-value success in one fn; F2 comptime-param enum value-failable read at runtime on the success
|
||||
path — `cast`, bare `if`, `== error.X`, plus the error path). Both read the slot at runtime so an
|
||||
`undef` is caught, not masked by the `if !e` proof. Fail on pre-fix code, pass after. Verified
|
||||
`zig build`, `zig build test`, and `bash tests/run_examples.sh` (453 ok) all green.
|
||||
|
||||
Below preserved as a record of the original problem.
|
||||
|
||||
## Symptom
|
||||
|
||||
A value-failable function `-> (EnumType, !ErrSet)` writes a **garbage nonzero tag into the error
|
||||
slot on the SUCCESS path**. Per specs.md the error channel must be `0` on success ("0 in the
|
||||
error slot means no error"). Every **runtime read** of the slot on success (`cast(i64) err`, bare
|
||||
`if err`, `err == error.X`, and therefore `error_tag_name(err)`) reports a false error. Only the
|
||||
path-sensitive compile-time proof `if !err` reads correctly (it is tied to the SSA value, not a
|
||||
runtime load of the slot), which is why it masks the bug.
|
||||
|
||||
- **Observed (enum value):** success path → error slot reads nonzero (garbage `undef`), not `0`.
|
||||
- **Expected:** success path → error slot reads `0`; `if err` is false; `err == error.X` is false.
|
||||
|
||||
## Reproduction (only imports `modules/std.sx`)
|
||||
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
Color :: enum { red; green; blue; }
|
||||
E :: error { Nope }
|
||||
|
||||
pick :: (s: string) -> (Color, !E) {
|
||||
if s == "red" { return .red; } // SUCCESS path
|
||||
raise error.Nope;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
c, e := pick("red"); // SUCCESS -> error slot MUST be 0
|
||||
print("error e (int) = {}\n", cast(i64) e); // EXPECT 0 ; BUG prints 1
|
||||
if e { print("bare-if e: ERROR (WRONG)\n"); } else { print("bare-if e: ok\n"); }
|
||||
if e == error.Nope { print("e == Nope (WRONG)\n"); } else { print("e != Nope (ok)\n"); }
|
||||
if !e { print("guard !e: value c (int) = {}\n", cast(i64) c); } // c = 0 = .red (CORRECT)
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Actual (buggy):**
|
||||
```text
|
||||
error e (int) = 1
|
||||
bare-if e: ERROR (WRONG)
|
||||
e == Nope (WRONG)
|
||||
guard !e: value c (int) = 0
|
||||
```
|
||||
**Expected (now produced):**
|
||||
```text
|
||||
error e (int) = 0
|
||||
bare-if e: ok
|
||||
e != Nope (ok)
|
||||
guard !e: value c (int) = 0
|
||||
```
|
||||
|
||||
## Contrast — the IDENTICAL shape with an i32 value is CORRECT
|
||||
|
||||
```sx
|
||||
pick :: (n: i32) -> (i32, !E) { if n > 0 { return n; } raise error.Nope; }
|
||||
// v, e := pick(5); → error slot = 0 (correct); bare-if e: ok
|
||||
```
|
||||
The split is **enum-value-specific** because only an enum literal (`return .variant`) resolves its
|
||||
tag against `target_type`. An integer literal does not, so the i32 path never got mis-stamped with
|
||||
the failable-tuple type and never took the false forwarding branch.
|
||||
|
||||
## Root cause (confirmed at ground truth)
|
||||
|
||||
`return .red` in `pick` lowered the enum literal with `target_type = (Color, !E)` (the whole
|
||||
failable tuple). The LLVM IR on the success path was:
|
||||
|
||||
```llvm
|
||||
ret { i64, i32 } { i64 0, i32 undef } ; error slot UNDEF, not 0 (.blue gave i64 0 too — value lost)
|
||||
```
|
||||
|
||||
vs. the i32 case which already produced `ret { i32, i32 } { i32 7, i32 0 }`. After narrowing the
|
||||
return target to the value type, the enum success path produces `ret { i64, i32 } zeroinitializer`
|
||||
(value 0 = `.red`, error slot 0), and `.blue` correctly carries ordinal 2.
|
||||
Reference in New Issue
Block a user