# 0118 — `cast(<compound type>) expr` dies with "unresolved 'unknown_expr'"

> **RESOLVED** (2026-06-11, same session, Agra-authorized in-session fix).
> Two-part root cause: (1) `lowerCall` lowers args BEFORE the cast handler,
> and compound type literals had NO expression-position lowering — they hit
> `lowerExpr`'s catch-all `unknown_expr` error. Fixed by giving the six
> compound type-expr shapes (`*T`, `[*]T`, `[]T`, `?T`, `[N]T`, fn types) a
> first-class `const_type` lowering arm in `src/ir/lower/expr.zig`,
> mirroring named types (`t : Type = *i64;` now works like `t : Type =
> f64;`). (2) The cast handler's private static-type gate only accepted
> bare names — replaced with the canonical `isStaticTypeArg`
> (`src/ir/lower/call.zig`), so static compound casts route through
> `coerceExplicit` while the runtime-`Type`-variable form (`cast(type) val`
> in category arms) still falls through to runtime dispatch. Regression
> test: examples/0182-types-cast-compound-types.sx (all compound cast
> forms + the first-class Type value). Suite 579/579.

## Symptom

`cast(T) expr` with a COMPOUND static type argument (`*i64`, `[]u8`, `?i32`,
`[*]i64`, `[4]i64`, …) fails to compile with a junk diagnostic pointing at the
type argument. Observed:

```
error: unresolved 'unknown_expr' (in probe.sx fn main)
  --> probe.sx:5:21
   |
 5 |     q : *i64 = cast(*i64) p;
   |                     ^^^^
```

Expected: the cast resolves the type argument statically and routes through
`coerceExplicit` (for `cast(*i64) p` where `p : *i64`, a no-op), exactly as it
does for bare names (`cast(i32) 3.14` works). The spec places no scalar-only
restriction on `cast(Type)` (specs.md "cast(Type) expr — prefix operator that
converts expr to Type").

Pre-existing on master (verified on a clean build of 679653f) — independent of
the in-flight const-pointer work; plain `*i64` reproduces it.

## Reproduction

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

main :: () {
    x := 42;
    p : *i64 = @x;
    q : *i64 = cast(*i64) p;   // error: unresolved 'unknown_expr'
    print("{}\n", q.*);        // expected: 42
}
```

## Investigation prompt

The `cast` handler in `src/ir/lower/call.zig` (~line 391, the
`Handle cast(TargetType, val)` block) gates static resolution with a PRIVATE
`is_static_type` check that only accepts `type_expr` and `identifier` AST
shapes. Every compound type-expression shape (`pointer_type_expr`,
`slice_type_expr`, `many_pointer_type_expr`, `optional_type_expr`,
`array_type_expr`, `function_type_expr`) falls through to the "runtime cast"
builtin path, which cannot resolve it and surfaces the catch-all
"unresolved 'unknown_expr'".

The codebase already has the canonical gate: `Lowering.isStaticTypeArg`
(`src/ir/lower/generic.zig:206`), which `type_name` / `type_eq` use — it
accepts the full compound-shape set (this is why `type_name(*i64)` folds
fine while `cast(*i64)` dies). The fix likely: replace the private
`is_static_type` block with `self.isStaticTypeArg(type_arg)` (keeping the
scope-shadow semantics: an identifier bound to a runtime `Type` variable must
still route to the runtime-dispatch path used by `case`-arm `cast(type)` —
that path is load-bearing for `any_to_string`, see specs.md "runtime generic
dispatch"). Then `resolveTypeArg` already handles the compound shapes.

Verification:
1. Run the repro above — expect `42`, exit 0.
2. Sanity: `cast([]u8)`, `cast(?i32)`, `cast([*]i64)` forms resolve.
3. `bash tests/run_examples.sh` — the `case`-arm runtime-dispatch examples
   (any_to_string formatting suite) must stay green, proving the
   runtime-`Type`-variable path still falls through to the builtin.
4. Pin the repro as a regression example per CLAUDE.md ("Resolving an open
   issue").

Context note: surfaced while probing PLAN-CONST-AGG step 5 (`*const T`), whose
const-discard diagnostic suggests "use an explicit cast (`xx`/`cast`)" — `xx`
works for pointer-shaped targets; `cast` currently does not.
