lang F1 2.7: pack-as-value diagnostics (Phase 2 complete)
Using a bare pack name where a runtime value is required was silent garbage (f(xs)/return xs produced a stray pointer). Now a clear, context-tailored compile error: isPackName + diagPackAsValue, caught at lowerVarDecl (storage), lowerReturn (return), lowerFor (iterate), and an identifier-arm catch-all for call/other. Storage binds a placeholder so there is no cascade error. Suggestions point at WORKING fixes -- materialize (..xs), or declare the slice form ..xs: []P for runtime use. The plan category-B "spread ..xs" is broken (spreading a comptime pack into a []Any param crashes the LLVM verifier; filed issue 0053), so the diagnostics steer to the slice-of-protocol variadic instead. Repurposed examples/162-pack-bare-args.sx (was an aspirational bare-$args->[]Any auto-materialise, contradicting Decision 1) into the slice-form forward (..args: []Any). examples/203 is the four-category negative test. specs.md "Pack as value" updated. 238 examples + unit green.
This commit is contained in:
@@ -1495,6 +1495,17 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
fn lowerVarDecl(self: *Lowering, vd: *const ast.VarDecl) void {
|
||||
if (vd.value) |val| {
|
||||
if (val.data == .identifier and self.isPackName(val.data.identifier.name)) {
|
||||
const ph = self.diagPackAsValue(val.data.identifier.name, val.span, .storage);
|
||||
// Bind the name to the placeholder so later uses don't cascade
|
||||
// into a second "unresolved" error after this one.
|
||||
if (self.scope) |scope| {
|
||||
scope.put(vd.name, .{ .ref = ph, .ty = .unresolved, .is_alloca = false });
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (vd.type_annotation) |ta| {
|
||||
// Explicit type annotation — resolve type first, then lower value
|
||||
const ty = self.resolveType(ta);
|
||||
@@ -1646,6 +1657,12 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
fn lowerReturn(self: *Lowering, rs: *const ast.ReturnStmt) void {
|
||||
if (rs.value) |val| {
|
||||
if (val.data == .identifier and self.isPackName(val.data.identifier.name)) {
|
||||
_ = self.diagPackAsValue(val.data.identifier.name, val.span, .return_value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Set target_type to function return type so null_literal etc. get the right type.
|
||||
// When inlining a comptime body, the *inlined* fn's declared return type wins
|
||||
// over the caller's — otherwise `return 42` inside a `-> s64` body lowered into
|
||||
@@ -2185,6 +2202,13 @@ pub const Lowering = struct {
|
||||
.undef_literal => self.builder.constUndef(self.target_type orelse .void),
|
||||
|
||||
.identifier => |id| blk: {
|
||||
// A bare pack name in value position has no runtime
|
||||
// representation (Decision 1). Projections (`xs.len`, `xs[i]`,
|
||||
// `xs.value`) are field/index nodes handled elsewhere, so a bare
|
||||
// `xs` reaching here is always a pack-as-value misuse.
|
||||
if (self.isPackName(id.name)) {
|
||||
break :blk self.diagPackAsValue(id.name, node.span, .generic);
|
||||
}
|
||||
if (self.scope) |scope| {
|
||||
if (scope.lookup(id.name)) |binding| {
|
||||
if (binding.is_alloca) {
|
||||
@@ -3223,6 +3247,11 @@ pub const Lowering = struct {
|
||||
if (fe.is_inline) return self.lowerInlineRangeFor(fe, end_node);
|
||||
return self.lowerRuntimeRangeFor(fe, end_node);
|
||||
}
|
||||
// Collection-form `for xs : (x)` over a pack: a pack has no runtime
|
||||
// value to iterate (Decision 1) — point the user at `inline for`.
|
||||
if (fe.iterable.data == .identifier and self.isPackName(fe.iterable.data.identifier.name)) {
|
||||
return self.diagPackAsValue(fe.iterable.data.identifier.name, fe.iterable.span, .runtime_iter);
|
||||
}
|
||||
|
||||
// Lower iterable
|
||||
const iterable = self.lowerExpr(fe.iterable);
|
||||
@@ -4877,6 +4906,31 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
const PackValueKind = enum { storage, call_arg, return_value, runtime_iter, generic };
|
||||
|
||||
/// `xs` is a pack name used where a runtime value is required. A pack is
|
||||
/// comptime-only (Decision 1), so this is an error — with a context-tailored
|
||||
/// suggestion for how to express the intent instead.
|
||||
fn diagPackAsValue(self: *Lowering, name: []const u8, span: ast.Span, kind: PackValueKind) Ref {
|
||||
if (self.diagnostics) |d| {
|
||||
const id = d.addFmtId(.err, span, "pack '{s}' has no runtime value — a pack is comptime-only and can't be used as a value here", .{name});
|
||||
switch (kind) {
|
||||
.storage => d.addHelpFmt(id, span, null, "to store it, materialize a tuple: `(..{s})`", .{name}),
|
||||
.call_arg => d.addHelpFmt(id, span, null, "to pass it on at runtime, declare `{s}` as a slice variadic `..{s}: []P` (a protocol slice) instead of a pack `..{s}: P`", .{ name, name, name }),
|
||||
.return_value => d.addHelpFmt(id, span, null, "to return it, return a tuple `(..{s})` and make the return type that tuple", .{name}),
|
||||
.runtime_iter => d.addHelpFmt(id, span, null, "to iterate at comptime use `inline for 0..{s}.len (i)`; for a runtime loop declare it as `..{s}: []P` (a protocol slice) instead of a pack", .{ name, name }),
|
||||
.generic => d.addHelpFmt(id, span, null, "materialize a tuple `(..{s})` to store it, or declare `{s}` as a slice variadic `..{s}: []P` for runtime use instead of a pack `..{s}: P`", .{ name, name, name, name }),
|
||||
}
|
||||
}
|
||||
return self.emitPlaceholder(name);
|
||||
}
|
||||
|
||||
/// True when `name` is a pack parameter bound in the current mono body.
|
||||
fn isPackName(self: *Lowering, name: []const u8) bool {
|
||||
const ppc = self.pack_param_count orelse return false;
|
||||
return ppc.contains(name);
|
||||
}
|
||||
|
||||
fn lowerSliceExpr(self: *Lowering, se: *const ast.SliceExpr) Ref {
|
||||
const obj = self.lowerExpr(se.object);
|
||||
const lo = if (se.start) |s| self.lowerExpr(s) else self.builder.constInt(0, .s64);
|
||||
|
||||
Reference in New Issue
Block a user