# 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 :: () -> s32 { 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 :: () -> s32 { 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(s32)` — 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.