# RESOLVED — 0111: unannotated int-literal locals adopt the enclosing fn's return type

**Root cause:** `lowerFunctionBodyInto` sets `self.target_type = ret_ty` for the whole
body (for the implicit trailing return), the `.int_literal` arm adopts any integer
`target_type`, and the unannotated paths of `lowerVarDecl` / `lowerDestructureDecl`
lowered their initializer without clearing it — so `x := 0` in a `-> i32`/`-> i8`
function was typed i32/i8 and `big := 3000000000` silently wrapped.

**Fix:** both decl paths now save/clear/restore `target_type` around the initializer
`lowerExpr` (`src/ir/lower/stmt.zig`); a declaration without annotation provides no
target, so literals take their spec defaults (i64/f64). Trailing-expression and
`return` coercion to the return type are untouched.

**Regression test:** `examples/0173-types-int-literal-default-i64.sx` (f.x/g.x/main.x,
destructured a/b all print `i64`; `big` prints `3000000000`).

**Note (not fixed here):** the `.int_literal` arm still wraps a literal that does not
fit an explicitly-annotated integer target (`x : i8 = 300`) with no diagnostic —
filed separately as issue 0112.

---

# 0111 — unannotated int-literal locals adopt the enclosing fn's return type (silent narrowing + wraparound)

**Symptom.** A local declared `x := <int literal>` (no annotation) inside a
function whose return type is an integer type T gets typed as **T**, not the
spec'd default. `f :: () -> i32 { x := 0; ... }` gives `x: i32`;
`g :: () -> i8 { x := 0; ... }` gives `x: i8`. Expected (specs.md §"Integer
literals default to `i64`", lines 240 / 1428): `i64` in all of these — the
declaration has no target type. Inside a `-> void` function the same decl
correctly infers `i64`.

Consequences are silent and severe:

- All arithmetic through such locals wraps at the narrowed width:
  `x := 0; x += 3000000000;` inside `main :: () -> i32` prints `-1294967296`.
- A large literal initializer truncates with **no diagnostic**:
  `big := 3000000000;` inside a `-> i32` fn binds `big` to i32 = `-1294967296`.
- Blast radius: every `main :: () -> i32 { ... }` in the corpus types every
  unannotated int-literal local as i32 — long-running counters/accumulators
  in such functions are one overflow away from wrong results. Discovered
  because issue 0109's verification loop (`sum` over 1M iterations in a
  `-> i32` main) printed the 32-bit-wrapped sum after the segfault was fixed.

## Reproduction

```sx
#import "modules/std.sx";

f :: () -> i32 {
    x := 0;
    print("f.x: {}\n", type_name(type_of(x)));
    0
}

g :: () -> i8 {
    x := 0;
    print("g.x: {}\n", type_name(type_of(x)));
    0
}

big_host :: () -> i32 {
    big := 3000000000;
    print("big: {} = {}\n", type_name(type_of(big)), big);
    0
}

main :: () {
    f();
    g();
    big_host();
    x := 0;
    print("main.x: {}\n", type_name(type_of(x)));
}
```

- **Observed** (current master): `f.x: i32`, `g.x: i8`,
  `big: i32 = -1294967296`, `main.x: i64`.
- **Expected**: `i64` for all four; `big` prints `3000000000`.

Repro co-located: `issues/0111-int-literal-local-adopts-fn-return-type.sx`
(standalone version of the above; unpinned until fixed).

## Root cause (traced)

Three-link chain, all confirmed by reading:

1. `src/ir/lower/decl.zig` ~2149 (`lowerFunctionBodyInto`): before lowering
   the body, `self.target_type = ret_ty` is set for the WHOLE body — intended
   for the implicit trailing-return coercion — and only restored after the
   body finishes.
2. `src/ir/lower/expr.zig` ~1499 (`.int_literal` arm): an int literal adopts
   `self.target_type` whenever it `isIntEx` — with no fits-check, so a
   too-big literal wraps silently.
3. `src/ir/lower/stmt.zig` ~335-348 (`lowerVarDecl`, unannotated path):
   lowers the initializer with whatever `target_type` is in scope and takes
   the decl's type from the lowered ref. The annotated path overwrites
   `target_type` with the annotation (which is why `y : i64 = 0` is immune);
   the unannotated path inherits the function-return context it should never
   see.

## Investigation prompt (paste into a fresh session)

> Fix issue 0111: unannotated int-literal locals adopt the enclosing
> function's integer return type instead of the i64 default. Root cause chain
> is in the issue (decl.zig ~2149 body-wide `target_type = ret_ty`;
> expr.zig ~1499 `.int_literal` adopting it; stmt.zig ~335 unannotated
> `lowerVarDecl` not clearing it). Fix shape: in `lowerVarDecl`'s
> unannotated path, save `self.target_type`, set it to `null` around the
> `lowerExpr(val)` of the initializer, restore after — a declaration without
> annotation provides no target. Audit the sibling statement positions that
> also shouldn't inherit the return-type context (e.g. expression statements,
> `lowerMultiAssign` / `lowerDestructureDecl` unannotated paths) and apply the
> same clear where applicable. Do NOT touch the trailing-expression /
> `return` paths — `f :: () -> i32 { 0 }` must keep coercing the tail to the
> return type.
>
> Separately consider (same fix or follow-up per scope): the `.int_literal`
> arm wraps a literal that doesn't fit the adopted integer target with no
> diagnostic — add a fits-check that errors like the float-narrowing rule
> (`floatToIntExact` precedent) instead of silently truncating.
>
> Verify: run `issues/0111-int-literal-local-adopts-fn-return-type.sx` —
> expect `i64` for f.x / g.x / big / main.x and `big = 3000000000`. Then
> `zig build && zig build test && bash tests/run_examples.sh` against
> EXISTING snapshots: any diff means an example was silently relying on
> narrowed locals — review each (the change is user-visible only via
> type_name/overflow behavior). Promote the repro per the resolution flow
> (`examples/01xx-types-...` block) and mark this issue RESOLVED.
>
> Context: this bug BLOCKS the in-flight fix session for issues 0108/0109/
> 0110 (for-loop codegen resource bugs). The 0109 emitter change (entry-block
> alloca hoisting) is already applied in the working tree, builds, and fixes
> both 0109 repros — but its regression example (1M-iteration `sum`
> accumulation in `main :: () -> i32`) cannot produce the documented expected
> output (`sum=499999500000`) until locals stop narrowing to i32, and
> suite/.ir snapshot regen must not run on a compiler with this live
> miscompile. After 0111 lands, resume: finalize 0109 (suite + .ir review +
> regression example + commit), then 0110, then 0108 per their issue files.
