fix(0118): cast accepts compound type args; compound type literals are first-class Type values
This commit is contained in:
27
examples/0182-types-cast-compound-types.sx
Normal file
27
examples/0182-types-cast-compound-types.sx
Normal file
@@ -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));
|
||||||
|
}
|
||||||
1
examples/expected/0182-types-cast-compound-types.exit
Normal file
1
examples/expected/0182-types-cast-compound-types.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
examples/expected/0182-types-cast-compound-types.stderr
Normal file
1
examples/expected/0182-types-cast-compound-types.stderr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
6
examples/expected/0182-types-cast-compound-types.stdout
Normal file
6
examples/expected/0182-types-cast-compound-types.stdout
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
a: 42
|
||||||
|
b: 42
|
||||||
|
c: 42
|
||||||
|
d: 7
|
||||||
|
e: 3 3
|
||||||
|
f: *s64
|
||||||
@@ -1,5 +1,20 @@
|
|||||||
# 0118 — `cast(<compound type>) expr` dies with "unresolved 'unknown_expr'"
|
# 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 = *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
|
## Symptom
|
||||||
|
|
||||||
`cast(T) expr` with a COMPOUND static type argument (`*s64`, `[]u8`, `?s32`,
|
`cast(T) expr` with a COMPOUND static type argument (`*s64`, `[]u8`, `?s32`,
|
||||||
|
|||||||
@@ -387,25 +387,16 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle cast(TargetType, val) — emit conversion instructions
|
// 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) {
|
if (std.mem.eql(u8, id.name, "cast") and c.args.len >= 2) {
|
||||||
const type_arg = c.args[0];
|
const type_arg = c.args[0];
|
||||||
const is_static_type = blk: {
|
if (self.isStaticTypeArg(type_arg)) {
|
||||||
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) {
|
|
||||||
const dst_ty = self.resolveTypeArg(c.args[0]);
|
const dst_ty = self.resolveTypeArg(c.args[0]);
|
||||||
const val = args.items[1]; // already lowered
|
const val = args.items[1]; // already lowered
|
||||||
const src_ty = self.inferExprType(c.args[1]);
|
const src_ty = self.inferExprType(c.args[1]);
|
||||||
|
|||||||
@@ -2021,6 +2021,25 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
|||||||
break :blk self.emitError(te.name, node.span);
|
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),
|
.try_expr => |te| self.lowerTry(te.operand, node.span),
|
||||||
.catch_expr => |ce| self.lowerCatch(&ce, node.span),
|
.catch_expr => |ce| self.lowerCatch(&ce, node.span),
|
||||||
.caller_location => self.lowerCallerLocation(node),
|
.caller_location => self.lowerCallerLocation(node),
|
||||||
|
|||||||
Reference in New Issue
Block a user