diff --git a/examples/0182-types-cast-compound-types.sx b/examples/0182-types-cast-compound-types.sx new file mode 100644 index 0000000..aa28652 --- /dev/null +++ b/examples/0182-types-cast-compound-types.sx @@ -0,0 +1,27 @@ +// `cast(T) expr` accepts any compile-time-resolvable type argument, +// including compound shapes: `*T`, `[*]T`, `?T`, `[]T`. The same lowering +// makes a compound type literal a first-class `Type` value in expression +// position (`t : Type = *s64;`), mirroring named types (`t : Type = f64;`). +// Regression (issue 0118): compound casts fell into the runtime-dispatch +// path and died with "unresolved 'unknown_expr'". + +#import "modules/std.sx"; + +main :: () { + x := 42; + p : *s64 = @x; + q : *s64 = cast(*s64) p; // no-op pointer cast + print("a: {}\n", q.*); + addr : s64 = xx p; + r : *s64 = cast(*s64) addr; // int → ptr through compound cast + print("b: {}\n", r.*); + mp : [*]s64 = cast([*]s64) p; // ptr → many-pointer + print("c: {}\n", mp[0]); + o : ?s32 = cast(?s32) 7; // optional wrap + print("d: {}\n", o ?? -1); + arr := .[1, 2, 3]; + s : []s64 = cast([]s64) arr; // array → slice + print("e: {} {}\n", s.len, s[2]); + t : Type = *s64; // first-class compound Type value + print("f: {}\n", type_name(t)); +} diff --git a/examples/expected/0182-types-cast-compound-types.exit b/examples/expected/0182-types-cast-compound-types.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/examples/expected/0182-types-cast-compound-types.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0182-types-cast-compound-types.stderr b/examples/expected/0182-types-cast-compound-types.stderr new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/0182-types-cast-compound-types.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0182-types-cast-compound-types.stdout b/examples/expected/0182-types-cast-compound-types.stdout new file mode 100644 index 0000000..dc81b77 --- /dev/null +++ b/examples/expected/0182-types-cast-compound-types.stdout @@ -0,0 +1,6 @@ +a: 42 +b: 42 +c: 42 +d: 7 +e: 3 3 +f: *s64 diff --git a/issues/0118-cast-compound-type-arg-unresolved.md b/issues/0118-cast-compound-type-arg-unresolved.md index 85778ee..e5ba36a 100644 --- a/issues/0118-cast-compound-type-arg-unresolved.md +++ b/issues/0118-cast-compound-type-arg-unresolved.md @@ -1,5 +1,20 @@ # 0118 — `cast() 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 = *s64;` 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 (`*s64`, `[]u8`, `?s32`, diff --git a/src/ir/lower/call.zig b/src/ir/lower/call.zig index 3153e4d..679041d 100644 --- a/src/ir/lower/call.zig +++ b/src/ir/lower/call.zig @@ -387,25 +387,16 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref { }; // Handle cast(TargetType, val) — emit conversion instructions - // Only for compile-time known types (type_expr or known type names) + // for any compile-time-resolvable type argument. The gate is the + // canonical `isStaticTypeArg` (the one `type_name`/`type_eq` use), + // so compound shapes (`*T`, `[]T`, `?T`, `[*]T`, `[N]T`) resolve + // statically instead of falling into the runtime-dispatch path + // and dying unresolved (issue 0118). An identifier bound in scope + // as a runtime `Type` value (the `cast(type) val` category-arm + // form) still classifies as non-static and falls through. if (std.mem.eql(u8, id.name, "cast") and c.args.len >= 2) { const type_arg = c.args[0]; - const is_static_type = blk: { - if (type_arg.data == .type_expr) break :blk true; - if (type_arg.data == .identifier) { - const tname = type_arg.data.identifier.name; - // Check if it's a known type name (not a runtime variable) - if (type_bridge.resolveTypePrimitive(tname) != null) break :blk true; - if (self.type_bindings) |bindings| { - if (bindings.get(tname) != null) break :blk true; - } - // Check if it's a registered struct/enum type name - const name_id = self.module.types.internString(tname); - if (self.module.types.findByName(name_id) != null) break :blk true; - } - break :blk false; - }; - if (is_static_type) { + if (self.isStaticTypeArg(type_arg)) { const dst_ty = self.resolveTypeArg(c.args[0]); const val = args.items[1]; // already lowered const src_ty = self.inferExprType(c.args[1]); diff --git a/src/ir/lower/expr.zig b/src/ir/lower/expr.zig index 3924eed..bd295e3 100644 --- a/src/ir/lower/expr.zig +++ b/src/ir/lower/expr.zig @@ -2021,6 +2021,25 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref { break :blk self.emitError(te.name, node.span); }, + // Compound type literals (`*T`, `[]T`, `[*]T`, `?T`, `[N]T`, fn types) + // in expression position are first-class `Type` values, exactly like + // the named form above (`t : Type = *s64;` ↔ `t : Type = f64;`). Also + // the path a static `cast(*s64) v` type argument takes — call args are + // lowered before the cast handler inspects the AST (issue 0118). + .pointer_type_expr, + .many_pointer_type_expr, + .slice_type_expr, + .optional_type_expr, + .array_type_expr, + .function_type_expr, + => blk: { + const ty = self.resolveTypeWithBindings(node); + // The resolver diagnosed any unresolved leaf; don't mint a Type + // value around the failure sentinel. + if (ty == .unresolved) break :blk self.emitError("unknown_expr", node.span); + break :blk self.builder.constType(ty); + }, + .try_expr => |te| self.lowerTry(te.operand, node.span), .catch_expr => |ce| self.lowerCatch(&ce, node.span), .caller_location => self.lowerCallerLocation(node),