# issue-0041 — Pointer types don't parse as expressions / type-argument positions

**FIXED.** `size_of(*u8)`, `align_of(*u8)`, and the alias form
`Ptr :: *u8;` all parse and lower correctly today. The fix is
in tree as part of broader parser/lowering work — no specific
commit isolates it, but the original repro now prints `8` and
returns 0 exit.

Below preserved as a record of the original problem.

## Symptom

A pointer type like `*u8` or `*void` does not parse in positions
where a type expression is expected as a *value*, e.g.:

- As an argument to a `$T: Type` builtin: `size_of(*u8)`,
  `align_of(*u8)`.
- On the RHS of a type alias: `Ptr :: *u8;`.

In each case the parser emits `error: unexpected token in expression`
at the column of the `*`.

Pointer types DO parse correctly in dedicated type-annotation
positions: function parameters (`(p: *u8)`), struct fields
(`field: *u8;`), variable annotations (`p: *u8 = ...;`). So the bug
is a parsing inconsistency between "type-annotation context" and
"expression context where a type is expected".

This is pre-existing — it affects `size_of` (already shipping) and
was just made more visible by adding `align_of` in Phase 0.6 of the
MEM plan. Not a regression introduced by 0.6, but a real limitation
worth pinning down because:

- Phase 1+ of the MEM plan will need `size_of(*T)` / `align_of(*T)`
  in user-facing allocator helpers if we want to stay terse — e.g.
  serializing a pointer-typed field in `field_value_int` patterns.
- It's a discoverability cliff. New users WILL write `size_of(*u8)`,
  see "unexpected token", and have to learn the workaround.

## Reproduction

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

main :: () -> i32 {
    n := size_of(*u8);   // error: unexpected token in expression
    print("{}\n", n);
    0;
}
```

Also fails on the alias form:

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

Ptr :: *u8;              // error: unexpected token in expression

main :: () -> i32 { 0; }
```

Both `sx run` and `sx build` reject identically.

## Confirmed working workarounds

A pointer type DOES resolve when bound through a `*void`-style
variable type and then cast, or routed via a helper:

```sx
// Workaround A: anonymous struct holding the pointer field, then
// pull alignment from the wrapping struct (clumsy).
Wrap :: struct { p: *u8; }
n := align_of(Wrap);          // 8 — correct for pointer alignment.

// Workaround B: explicit *void
n := size_of(*void);          // ALSO fails — same parse error.
```

Workaround B is NOT functional — it has the same parse error. Only
the wrap-in-struct or type-alias-via-typedef trick is currently
viable for code that needs pointer size/alignment.

There is no clean way today to write `size_of(*u8)`. The whole
class of "ptr type as type-expression value" is unsupported.

## Investigation prompt

> Pointer types parse via a dedicated `parseTypeExpr` (or similar)
> path that the parser invokes in type-annotation positions (param
> lists, field declarations, variable annotations). The expression
> grammar used in argument positions (e.g. inside `size_of(...)`)
> dispatches through `parseExpr` instead, which treats `*` as
> "either prefix unary deref or infix multiplication" — neither
> matches the desired "type literal" interpretation.
>
> The fix likely belongs in the call-argument parser path: when
> the callee is a builtin that takes `$T: Type`, OR more broadly
> whenever the parser sees a `*` at the start of an expression
> followed by an identifier that resolves to a type, it should
> dispatch to `parseTypeExpr` instead of `parsePrefixUnary`.
>
> Implementation sketch:
> - Check `src/parser.zig` for the expression entry point that
>   handles `*` prefix. Today it likely returns a `unary_op
>   { op = deref, operand = … }` AST node.
> - Look at how lower.zig's `resolveTypeArg` consumes the AST node
>   for `size_of(i32)` — what AST shape does it expect for a type
>   literal? Probably an `identifier` whose name resolves to a type.
> - The fix should extend `resolveTypeArg` to also accept a
>   `unary_op { op = deref, ... }` and treat it as "pointer to
>   resolved type" — equivalent to `Ptr$T` in spec terms.
> - For the type-alias case (`Ptr :: *u8;`), the RHS of a `::`
>   const decl is parsed as an expression. The parser needs to
>   recognize that the LHS-determined shape (type-level alias)
>   should bias the RHS parser toward `parseTypeExpr`. Or: extend
>   the constant-fold path to interpret `unary_op { deref, T }` as
>   a type literal when used as a type.
>
> Verification:
> 1. Add `examples/issue-0041.sx` with the repro above and
>    `tests/expected/issue-0041.txt` capturing the expected output
>    (`size_of(*u8) → 8`).
> 2. Confirm `bash tests/run_examples.sh` still passes everything
>    else (151 tests currently).
> 3. Run `tools/verify-step.sh` to confirm chess on three platforms.
> 4. Also bake into `examples/50-smoke.sx` near the existing
>    `align_of` lines — add `align_of(*u8)`, `size_of(*u8)`,
>    `align_of(*void)` and regen.
>
> Hazard: any change to expression parsing affects a huge surface.
> Watch for these contexts to make sure they still work post-fix:
>   - `a * b` (multiplication)
>   - `*p` (prefix deref read)
>   - `*p = …` (prefix deref write)
>   - `func(a, *b)` (deref as argument)
> A surgical "is the next token a built-in type identifier" lookahead
> at the `*` site is probably less invasive than a wholesale
> type-expression-in-expression-position rewrite.

## Plan-level impact

None for Phase 0.6 — `align_of` shipped and works for every shape
that `size_of` works for (primitives, structs, type aliases through
non-pointer types). The 50-smoke test addition uses only
non-pointer types, so it's stable.

Phase 1+ should bake an `align_of(*u8)` test once the parser fix
lands, since the allocator API will want to round-trip pointer
alignments at some call sites.
