Read `expr as &Trait` as a cast, not as a name

`as` was already in the lexer's keyword list. Nothing in the EXPRESSION
grammar consumed it. The postfix loop broke on it, the statement reader took
the leftover `as` as the start of the next expression, and the binding walk
resolved it as an ordinary identifier:

    69:38 - error: undefined name 'as' (UndefinedName) [structural+type]

A name that never existed, reported as actionable, on a program `moon check`
accepts with zero errors. Same shape as `guard` (0.4.21) and `raise`
(0.4.23): a keyword the lexer knows and the parser does not.

Settled against the compiler rather than assumed -- `_verify/settle_0428_cast.ps1`
compiles `b as &Sink`, `Box::{ n: 2 } as &Sink` and `Box::{ ..o, n: 9 } as &Sink`,
and requires both `b as` and the non-trait `b as Box` to be rejected. `moon
check` spells the rule out: "`Int` is not a trait object type, it cannot be used
on the right hand side of `as`". Two earlier drafts of that settle established
nothing: one used `fn() => 1` as a lambda (MoonBit lambdas use braces; that is a
3002), and one used `as Int` as the target, so every case died on the
trait-object rule before `as` was ever the question.

The fix is `ECast(Expr)` plus an `as` branch in `parse_postfix_from`. The target
type is read by the existing `type_annotation` and DISCARDED. That is deliberate
and it is the interesting part: `as` requires a trait object, so the type looks
checkable, and it is not -- `IfaceSymTab` keeps no trait/struct distinction, so a
check built on it would report every cast to a struct type. Carrying a field no
lens can consult is worse than not carrying it. The span is the `as` token rather
than the operand's, because the operand survives as a child and so keeps its own
position, while `as`'s is recorded nowhere else.

The operand is walked, typed and evaluated. Walking it is not optional:
`fn to_sink(b : Box) -> &Sink { b as &Sink }` reads `b` and nothing else does, so
an arm that returned without walking would report the parameter as never used --
trading one false positive for another.

Measured by the tool's own output rather than a matcher, because the analyzer
already emits one finding per occurrence and the finding names the token:
`undefined name 'as'` is exactly ONE across the six corpora, at wzzc-dev/moui
`runtime/view_tree.mbt:69:38`. It was also moui's entire actionable count.

Corpus A/B over one unchanged tree, two builds: moui actionable 1 -> 0, the other
five byte-identical. Self-scan 68/68 parsed, actionable = 0.

Tests js 442 (12 new) / wasm 514 / wasm-gc 442, all green under `moon test
--deny-warn`; `moon check --deny-warn`, `moon fmt --check` and the wasm build are
clean, which is what gate.yml runs.

Five mutations: four killed, every restore byte-identical by SHA256, and the
gate now aborts unless the UNMUTATED suite is green first -- an earlier run
reported "M4 KILLED, 2 of 442 red" while the two tests doing the killing were
failing on clean code, so every count it printed was meaningless.

M4 (interp stops evaluating the operand) survives and is gated as an expected
survivor. `_verify/probe_0429_m4.ps1` applies it, rebuilds and diffs real analyzer
output on five programs: all five byte-identical, because an unbound name inside
a cast operand is reported by walk at the same family and span, and FMethodError
and FOpMismatch share the "arg-type" merge group. That is recorded as "no fixture
I could build observes it", NOT as proven equivalence over the input space.