fix(ir): reject non-type elements in tuple-literal-as-type (issue 0067)
`size_of((s32, 1))` treated the tuple literal as a tuple TYPE: for the non-type element `1` it emitted a `std.debug.print` and substituted `.s64` for that field, then compiled and printed a bogus size — a silent fabricated type (the forbidden silent-fallback pattern). Fix: - type_bridge.resolveTupleLiteralAsType: a non-type element now yields `.unresolved` (no `.s64`, no debug print) — it refuses to fabricate a tuple. type_bridge is stateless, so this is the binding-free backstop. - New stateful Lowering.resolveTupleLiteralTypeArg validates each element via isTypeShapedAstNode, emits a user-facing diagnostic at the offending element's span, and returns `.unresolved`. Wired into resolveTypeArg (size_of/align_of/…) and the resolveTypeWithBindings name-fallback; type_bridge builds the tuple only after validation passes. Regression: examples/1116-diagnostics-tuple-type-nontype-element-rejected.sx (exit 1 + diagnostic). Valid `(s32, s32)` still works (0115). Gate: zig build, zig build test, run_examples 351/0.
This commit is contained in:
@@ -11586,6 +11586,25 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a tuple LITERAL used in a type position (`(s32, s32)` reinterpreted
|
||||
/// as a tuple type at a type-demanding site such as `size_of`). Every element
|
||||
/// must itself denote a type; a non-type element — e.g. the `1` in
|
||||
/// `(s32, 1)` — is a user error. Emit a diagnostic pointing at the offending
|
||||
/// element and return `.unresolved`; never fabricate a tuple with a bogus
|
||||
/// field (issue 0067). type_bridge.resolveAstType builds the tuple only after
|
||||
/// this validation passes.
|
||||
fn resolveTupleLiteralTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||
for (node.data.tuple_literal.elements) |el| {
|
||||
if (!type_bridge.isTypeShapedAstNode(el.value, &self.module.types)) {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, el.value.span, "tuple type element is not a type (found `{s}`); a tuple used as a type must list only types, e.g. `(s32, s32)`", .{@tagName(el.value.data)});
|
||||
}
|
||||
return .unresolved;
|
||||
}
|
||||
}
|
||||
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
|
||||
}
|
||||
|
||||
fn resolveTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||
// Pack-index access in a type-arg slot (e.g. `type_name($args[0])`
|
||||
// or `type_eq($args[i], s64)`). Same shape as the
|
||||
@@ -11662,13 +11681,13 @@ pub const Lowering = struct {
|
||||
// Handle type constructor calls: size_of(Sx(f32)), size_of(Complex(u32))
|
||||
return self.resolveTypeCallWithBindings(&cl);
|
||||
},
|
||||
.tuple_literal => return self.resolveTupleLiteralTypeArg(node),
|
||||
.pointer_type_expr,
|
||||
.many_pointer_type_expr,
|
||||
.array_type_expr,
|
||||
.slice_type_expr,
|
||||
.optional_type_expr,
|
||||
.function_type_expr,
|
||||
.tuple_literal,
|
||||
=> return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map),
|
||||
else => return .unresolved,
|
||||
}
|
||||
@@ -12854,6 +12873,10 @@ pub const Lowering = struct {
|
||||
switch (node.data) {
|
||||
.type_expr => |te| return self.typeResolver().resolveName(te.name),
|
||||
.identifier => |id| return self.typeResolver().resolveName(id.name),
|
||||
// A non-spread tuple literal in a type position is a tuple-type
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user