fix(ir): exhaustive named-const array dims (0083) + nested slice-literal coercion (0085)

Makes the F0.4 fixes exhaustive across every resolution / nesting path.

0083 — named-const array dimension, stateless paths. Attempt 1 fixed the
stateful resolver (direct local decls, struct fields, params, returns) but the
binding-free registration-time resolver (`type_bridge`, used for type aliases
`Arr :: [N]T` and inline union/enum field types) still resolved a named dim with
a silent `else 0`, so `Arr :: [N]s64; a : Arr` and `union { a: [N]s64 }` were
still miscompiled (garbage / bus error). Thread the module-global const table
(`ProgramIndex.module_const_map`) into `type_bridge` alongside the alias map, so
`StatelessInner.resolveArrayLen` resolves a named module-const dim to the same
length everywhere. The remaining unresolvable case (a computed/comptime dim on
the binding-free path, which the stateful path hard-errors) now bails LOUDLY
instead of fabricating a 0 length.

0085 — nested slice-literal elements. `lowerArrayLiteral` lowered each element
with the element type as target but appended the raw value. A nested `.[...]`
element at a slice element type (`[][]s64`) still lowers to an aggregate array
`[N]T`, so the outer aggregate held raw arrays where slice {ptr,len} headers
were expected — indexing the inner slice read a garbage pointer and segfaulted.
After lowering each element, coerce a same-element array to the slice element
type via the existing `array_to_slice` op. The coercion recurses with the
nesting, so `[][]T` and deeper materialize at every level — local-bound AND
direct-call-argument forms.

Regressions (fail-before/pass-after demonstrated on the pre-fix compiler):
  examples/0140-types-named-const-array-dim.sx — extended with type-alias,
    nested [N][M]T, and union-field named dims (s64 / string / struct elems)
  examples/0142-types-nested-slice-literal-elements.sx — [][]s64 + [][]string,
    local-bound vs direct-arg
  src/ir/type_bridge.test.zig — named-const dim resolves to literal length

Gate: zig build, zig build test, bash tests/run_examples.sh (388 passed).
Issues 0083 and 0085 marked RESOLVED.
This commit is contained in:
agra
2026-06-04 09:06:08 +03:00
parent 12552e125d
commit 1f9f944ca1
13 changed files with 329 additions and 92 deletions

View File

@@ -560,9 +560,9 @@ pub const Lowering = struct {
} else if (cd.value.data == .struct_decl) {
self.registerStructDecl(&cd.value.data.struct_decl);
} else if (cd.value.data == .enum_decl) {
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
} else if (cd.value.data == .union_decl) {
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
} else if (cd.value.data == .comptime_expr) {
self.lowerComptimeGlobal(cd.name, cd.value.data.comptime_expr.expr, cd.type_annotation);
}
@@ -574,10 +574,10 @@ pub const Lowering = struct {
self.registerStructDecl(&sd);
},
.enum_decl => {
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
},
.union_decl => {
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
},
.error_set_decl => {
self.registerErrorSetDecl(decl);
@@ -675,10 +675,10 @@ pub const Lowering = struct {
self.registerStructDecl(&cd.value.data.struct_decl);
} else if (cd.value.data == .enum_decl) {
// Register enum/tagged-union types in the type table
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
} else if (cd.value.data == .union_decl) {
// Register plain union types in the type table
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
} else if (cd.value.data == .type_expr or
cd.value.data == .pointer_type_expr or
cd.value.data == .many_pointer_type_expr or
@@ -688,7 +688,7 @@ pub const Lowering = struct {
cd.value.data == .function_type_expr)
{
// Type alias: MyFloat :: f64; Ptr :: *u8; Cb :: (s32) -> s32;
const target_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
const target_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
self.program_index.type_alias_map.put(cd.name, target_ty) catch {};
} else if (cd.value.data == .identifier) {
// Identifier-RHS alias: MyAlias :: MyInt; WideAlias :: Wide;
@@ -771,7 +771,7 @@ pub const Lowering = struct {
// resolve via type_bridge and register the result
// under the alias name so `Vec4` in expression
// position can `const_type(<vector tid>)`.
const result_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
const result_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
if (result_ty != .void and result_ty != .unresolved) {
self.program_index.type_alias_map.put(cd.name, result_ty) catch {};
}
@@ -806,11 +806,11 @@ pub const Lowering = struct {
},
.enum_decl => {
// Register enum/tagged-union types in the type table
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
},
.union_decl => {
// Register plain union types in the type table
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
},
.error_set_decl => {
self.registerErrorSetDecl(decl);
@@ -1813,7 +1813,7 @@ pub const Lowering = struct {
// Block-local type declarations
.struct_decl => |sd| self.registerStructDecl(&sd),
.enum_decl, .union_decl => {
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
},
.error_set_decl => self.registerErrorSetDecl(node),
.ufcs_alias => |ua| {
@@ -1972,7 +1972,7 @@ pub const Lowering = struct {
return;
}
if (cd.value.data == .enum_decl or cd.value.data == .union_decl) {
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
return;
}
@@ -2912,7 +2912,7 @@ pub const Lowering = struct {
// `t : Type = f64;` store a real TypeId; lets
// `t == f64` icmp at runtime against the same TypeId.
if (self.isKnownTypeName(te.name)) {
const ty = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
const ty = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
break :blk self.builder.constType(ty);
}
break :blk self.emitError(te.name, node.span);
@@ -5357,8 +5357,26 @@ pub const Lowering = struct {
for (al.elements) |elem| {
const old_tt = self.target_type;
self.target_type = elem_ty;
const val = self.lowerExpr(elem);
var val = self.lowerExpr(elem);
self.target_type = old_tt;
// A nested `.[...]` element at a slice element type lowers to an
// aggregate array `[N]U` (lowerArrayLiteral always yields an array
// value); materialize it into a `[]U` slice so the element is a real
// {ptr,len} header rather than a raw array the callee would read its
// header off of (issue 0085). This per-element coercion recurses with
// the literal nesting, so `[][]T` and deeper coerce at every level.
if (!elem_ty.isBuiltin()) {
const ei = self.module.types.get(elem_ty);
if (ei == .slice) {
const val_ty = self.builder.getRefType(val);
if (!val_ty.isBuiltin()) {
const vi = self.module.types.get(val_ty);
if (vi == .array and vi.array.element == ei.slice.element) {
val = self.coerceToType(val, val_ty, elem_ty);
}
}
}
}
elems.append(self.alloc, val) catch unreachable;
}
@@ -5401,7 +5419,7 @@ pub const Lowering = struct {
const name_id = self.module.types.internString(id.name);
return self.module.types.findByName(name_id) orelse .unresolved;
},
.type_expr => return type_bridge.resolveAstType(te, &self.module.types, &self.program_index.type_alias_map),
.type_expr => return type_bridge.resolveAstType(te, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map),
.field_access => |fa| {
// Module.Type — try to resolve the field as a type name
const name_id = self.module.types.internString(fa.field);
@@ -6875,7 +6893,7 @@ pub const Lowering = struct {
// Check for #compiler free functions
if (self.program_index.fn_ast_map.get(func_name)) |fd_check| {
if (fd_check.body.data == .compiler_expr) {
const ret_ty = if (fd_check.return_type) |rt| type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map) else TypeId.void;
const ret_ty = if (fd_check.return_type) |rt| type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map) else TypeId.void;
return self.builder.compilerCall(func_name, args.items, ret_ty);
}
}
@@ -7230,7 +7248,7 @@ pub const Lowering = struct {
if (self.program_index.fn_ast_map.get(qualified)) |method_fd| {
if (method_fd.body.data == .compiler_expr) {
const ret_ty = if (method_fd.return_type) |rt|
type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map)
type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map)
else
.void;
return self.builder.compilerCall(qualified, method_args.items, ret_ty);
@@ -7668,7 +7686,7 @@ pub const Lowering = struct {
const ret_ty = blk: {
if (lam.return_type) |rt| {
break :blk type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map);
break :blk type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
}
// Use target closure return type if available — but only when it's
// a resolved type. An `.unresolved` ret comes from an unbound
@@ -8238,7 +8256,7 @@ pub const Lowering = struct {
}
fn resolveReturnType2(self: *Lowering, rt: ?*const Node) TypeId {
if (rt) |r| return type_bridge.resolveAstType(r, &self.module.types, &self.program_index.type_alias_map);
if (rt) |r| return type_bridge.resolveAstType(r, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
return .void;
}
@@ -9278,8 +9296,8 @@ pub const Lowering = struct {
const ret_ty: TypeId = blk: {
if (fd.return_type) |rt| {
if (rt.data == .type_expr) {
if (type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map) != .unresolved) {
break :blk type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map);
if (type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map) != .unresolved) {
break :blk type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
}
}
}
@@ -10551,7 +10569,7 @@ pub const Lowering = struct {
return .unresolved;
}
}
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
}
pub fn resolveTypeArg(self: *Lowering, node: *const Node) TypeId {
@@ -10612,7 +10630,7 @@ pub const Lowering = struct {
},
.type_expr => |te| {
if (self.program_index.type_alias_map.get(te.name)) |alias_ty| return alias_ty;
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
},
.call => |cl| {
// `type_of(x)` resolves to `inferExprType(x)` at lower
@@ -10637,7 +10655,7 @@ pub const Lowering = struct {
.slice_type_expr,
.optional_type_expr,
.function_type_expr,
=> return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map),
=> return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map),
else => return .unresolved,
}
}
@@ -11764,7 +11782,7 @@ pub const Lowering = struct {
// literal (`(s32, s32)`); validate its elements are types and reject
// non-type elements loudly (issue 0067).
.tuple_literal => return self.resolveTupleLiteralTypeArg(node),
else => return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map),
else => return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map),
}
}
@@ -12195,7 +12213,7 @@ pub const Lowering = struct {
}
return;
}
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
}
fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl) void {
@@ -12341,7 +12359,7 @@ pub const Lowering = struct {
if (const_node.data == .const_decl) {
const cd = const_node.data.const_decl;
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sd.name, cd.name }) catch continue;
const ty: ?TypeId = if (cd.type_annotation) |ta| type_bridge.resolveAstType(ta, table, &self.program_index.type_alias_map) else null;
const ty: ?TypeId = if (cd.type_annotation) |ta| type_bridge.resolveAstType(ta, table, &self.program_index.type_alias_map, &self.program_index.module_const_map) else null;
self.struct_const_map.put(qualified, .{ .value = cd.value, .ty = ty }) catch {};
}
}