mem: interp sweep — every silent arm now bails with a named reason
Apply the new CLAUDE.md "no silent unimplemented arms" rule to the interp. Every `else => return error.CannotEvalComptime` and `else => return val` (passthrough) gets a one-line `bailDetail` that surfaces through `printInterpBailDiag` as `op=X/X: <reason>` instead of a bare `CannotEvalComptime`. Tightened sites: - `.deref` else-arm used to return the operand unchanged for ANY Value kind. Now: enumerated allow-list (`.aggregate`, `.string` are legitimate pre-dereferenced values); scalars / handles / undef / null bail loudly. Previously, dereffing e.g. a `.boolean` silently produced a bare `.boolean` and the caller treated it as a successful deref. - `.unbox_any` else-arm used to return the operand unchanged for any non-aggregate. Now: enumerated bails for scalars / handles / void. An unbox_any whose operand wasn't routed through `box_any` first is a frontend bug and now shows up as one. - `.compiler_call` for an unregistered hook silently returned `CannotEvalComptime`. Now names the missing hook category in the detail. - `.length` / `.data_ptr` / `.subslice` / `.array_to_slice` / `.global_addr` / `.call_indirect` / `struct_get` / `enum_tag` / `enum_payload` / `unary -` / `field_name_get` / `field_value_get` / `objc_msg_send` / `jni_msg_send`: every `else` arm now carries a specific reason. - `evalArith` / `evalCmp` use `typeErrorDetail` so mismatched operand pairs surface "neither both-int nor both-float-coercible" instead of bare TypeError. - `callForeign` distinguishes "dlsym error" vs "symbol not found" vs "> 8 args" instead of returning the same error for all three. - `execBuiltin` arms for ops the lowering shouldn't have emitted at comptime (`.cast`, `.type_of`, `.alloc`, `.dealloc`) bail with a reason instead of a bare error. 154/154 still passing. Behavioural change: the `.deref` / `.unbox_any` arms used to silently produce a value for Value kinds they shouldn't have accepted. Any consumer relying on that silent fall-through now bails — which is the point.
This commit is contained in:
@@ -414,12 +414,13 @@ pub const Interpreter = struct {
|
||||
|
||||
fn callForeign(self: *Interpreter, func: *const inst_mod.Function, args: []const Value) InterpError!Value {
|
||||
const name = self.module.types.getString(func.name);
|
||||
const symbol = (host_ffi.lookupSymbol(self.alloc, name) catch return error.CannotEvalComptime) orelse {
|
||||
const symbol = (host_ffi.lookupSymbol(self.alloc, name) catch return bailDetail("comptime foreign call: dlsym error looking up symbol")) orelse {
|
||||
if (last_bail_detail == null) last_bail_detail = "comptime foreign call: symbol not found via dlsym (target-specific binding called at compile time?)";
|
||||
return error.CannotEvalComptime;
|
||||
};
|
||||
|
||||
var packed_args: [8]usize = undefined;
|
||||
if (args.len > packed_args.len) return error.CannotEvalComptime;
|
||||
if (args.len > packed_args.len) return bailDetail("comptime foreign call: more than 8 args (host_ffi trampolines max out at 8)");
|
||||
|
||||
var tmp = std.ArrayList([]u8).empty;
|
||||
defer {
|
||||
@@ -598,7 +599,7 @@ pub const Interpreter = struct {
|
||||
return .{ .value = switch (val) {
|
||||
.int => |v| .{ .int = -v },
|
||||
.float => |v| .{ .float = -v },
|
||||
else => return error.TypeError,
|
||||
else => return typeErrorDetail("comptime unary `-`: operand is neither int nor float"),
|
||||
} };
|
||||
},
|
||||
|
||||
@@ -763,7 +764,7 @@ pub const Interpreter = struct {
|
||||
if (fa.field_index == 0) return .{ .value = .{ .int = v } };
|
||||
return error.OutOfBounds;
|
||||
},
|
||||
else => return error.TypeError,
|
||||
else => return typeErrorDetail("comptime struct_get: base has no fields (not an aggregate/string/int)"),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -784,10 +785,10 @@ pub const Interpreter = struct {
|
||||
switch (val) {
|
||||
.int => return .{ .value = val },
|
||||
.aggregate => |fields| {
|
||||
if (fields.len == 0) return error.TypeError;
|
||||
if (fields.len == 0) return typeErrorDetail("comptime enum_tag: aggregate operand has zero fields");
|
||||
return .{ .value = fields[0] };
|
||||
},
|
||||
else => return error.TypeError,
|
||||
else => return typeErrorDetail("comptime enum_tag: operand is neither an int (untagged enum) nor an aggregate (tagged union)"),
|
||||
}
|
||||
},
|
||||
.enum_payload => |fa| {
|
||||
@@ -797,7 +798,7 @@ pub const Interpreter = struct {
|
||||
if (fa.field_index + 1 >= fields.len) return error.OutOfBounds;
|
||||
return .{ .value = fields[fa.field_index + 1] };
|
||||
},
|
||||
else => return error.TypeError,
|
||||
else => return typeErrorDetail("comptime enum_payload: base is not a tagged-union aggregate"),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -848,9 +849,9 @@ pub const Interpreter = struct {
|
||||
// The Obj-C runtime isn't available at comptime; any
|
||||
// `#objc_call` reached during `#run` execution can't
|
||||
// resolve. Fail fast so callers see a useful diagnostic.
|
||||
.objc_msg_send => return error.CannotEvalComptime,
|
||||
.objc_msg_send => return bailDetail("#objc_call not available at comptime (no Obj-C runtime)"),
|
||||
// Same story for JNI — no JVM at compile time.
|
||||
.jni_msg_send => return error.CannotEvalComptime,
|
||||
.jni_msg_send => return bailDetail("#jni_call not available at comptime (no JVM)"),
|
||||
|
||||
// ── Block params ────────────────────────────────────
|
||||
.block_param => {
|
||||
@@ -949,11 +950,17 @@ pub const Interpreter = struct {
|
||||
resolved_args.append(self.alloc, frame.getRef(arg)) catch return error.CannotEvalComptime;
|
||||
}
|
||||
if (self.build_config) |bc| {
|
||||
const result = hook(self, resolved_args.items, bc, self.alloc) catch return error.CannotEvalComptime;
|
||||
const result = hook(self, resolved_args.items, bc, self.alloc) catch return bailDetail("#compiler hook returned an error (see hook impl)");
|
||||
return .{ .value = result };
|
||||
}
|
||||
return .{ .value = .void_val };
|
||||
}
|
||||
if (last_bail_detail == null) {
|
||||
// Capture which hook name failed so the host diag
|
||||
// surfaces "compiler_call: unknown hook 'X'" instead
|
||||
// of a bare CannotEvalComptime.
|
||||
last_bail_detail = "#compiler hook not registered (likely a target-specific BuildOptions setter)";
|
||||
}
|
||||
return error.CannotEvalComptime;
|
||||
},
|
||||
|
||||
@@ -1048,7 +1055,7 @@ pub const Interpreter = struct {
|
||||
}
|
||||
return .{ .value = .{ .int = @intCast(fields.len) } };
|
||||
},
|
||||
else => return error.CannotEvalComptime,
|
||||
else => return bailDetail("comptime .len: operand is neither a string nor an aggregate"),
|
||||
}
|
||||
},
|
||||
.data_ptr => |u| {
|
||||
@@ -1059,7 +1066,7 @@ pub const Interpreter = struct {
|
||||
return error.OutOfBounds;
|
||||
},
|
||||
.string => return .{ .value = val },
|
||||
else => return error.CannotEvalComptime,
|
||||
else => return bailDetail("comptime .ptr: operand has no data field (not a string or slice aggregate)"),
|
||||
}
|
||||
},
|
||||
.subslice => |sub| {
|
||||
@@ -1072,7 +1079,7 @@ pub const Interpreter = struct {
|
||||
if (hi > s.len) return error.OutOfBounds;
|
||||
return .{ .value = .{ .string = s[lo..hi] } };
|
||||
}
|
||||
return error.CannotEvalComptime;
|
||||
return bailDetail("comptime subslice: base is not a string-backed value (slice over non-string aggregates not yet supported)");
|
||||
},
|
||||
|
||||
// ── Addr/deref ─────────────────────────────────────
|
||||
@@ -1091,10 +1098,20 @@ pub const Interpreter = struct {
|
||||
.int => return bailDetail("comptime deref through raw host pointer not supported (IR type width not threaded)"),
|
||||
.byte_ptr => return bailDetail("comptime deref through raw byte pointer not supported"),
|
||||
.heap_ptr => return bailDetail("comptime deref through interp heap pointer not supported"),
|
||||
// Other Value kinds (aggregate, string, int constants
|
||||
// used as identity-pointers in protocol thunks, etc.)
|
||||
// pass through — they're already the dereferenced form.
|
||||
else => return .{ .value = val },
|
||||
// Pre-dereferenced values that flow through deref as a
|
||||
// no-op: an aggregate/string already IS the loaded
|
||||
// value (lowering sometimes emits `deref(struct_val)`
|
||||
// where the struct was previously materialized in
|
||||
// place rather than via a slot).
|
||||
.aggregate, .string => return .{ .value = val },
|
||||
// Null deref is UB at runtime; surface it at comptime
|
||||
// instead of silently producing a null again.
|
||||
.null_val => return bailDetail("comptime deref of null"),
|
||||
// Scalars / handles / undef aren't pointer-shaped —
|
||||
// dereffing them is a frontend bug. Bail rather than
|
||||
// returning the bare value (which looked like a
|
||||
// successful deref to callers).
|
||||
.boolean, .float, .func_ref, .closure, .type_tag, .void_val, .undef => return bailDetail("comptime deref: operand is not a pointer"),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1168,7 +1185,14 @@ pub const Interpreter = struct {
|
||||
if (fields.len >= 1) return .{ .value = fields[0] };
|
||||
return error.OutOfBounds;
|
||||
},
|
||||
else => return .{ .value = val },
|
||||
// Any-typed comptime values flow through box_any first,
|
||||
// which always wraps as an aggregate. If we reach here
|
||||
// with a scalar / undef / null, the IR shape upstream
|
||||
// diverged from the box_any contract — bail loudly so
|
||||
// the offending box_any site shows in the diagnostic.
|
||||
.int, .float, .boolean, .string, .null_val, .undef => return bailDetail("comptime unbox_any: operand is a bare scalar (expected { tag, value } aggregate from box_any)"),
|
||||
.void_val => return bailDetail("comptime unbox_any: operand is void_val"),
|
||||
.slot_ptr, .heap_ptr, .byte_ptr, .func_ref, .closure, .type_tag => return bailDetail("comptime unbox_any: operand is a pointer/handle (expected { tag, value } aggregate)"),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1177,14 +1201,14 @@ pub const Interpreter = struct {
|
||||
const idx_val = frame.getRef(fr.index);
|
||||
const idx: usize = @intCast(switch (idx_val) {
|
||||
.int => |i| i,
|
||||
else => return error.CannotEvalComptime,
|
||||
else => return bailDetail("comptime field_name(T, i): index operand is not an int"),
|
||||
});
|
||||
const info = self.module.types.get(fr.struct_type);
|
||||
const fields = switch (info) {
|
||||
.@"struct" => |s| s.fields,
|
||||
.@"union" => |u| u.fields,
|
||||
.tagged_union => |u| u.fields,
|
||||
else => return error.CannotEvalComptime,
|
||||
else => return bailDetail("comptime field_name(T, i): T is not a struct/union/tagged_union"),
|
||||
};
|
||||
if (idx >= fields.len) return error.OutOfBounds;
|
||||
const name = self.module.types.getString(fields[idx].name);
|
||||
@@ -1195,7 +1219,7 @@ pub const Interpreter = struct {
|
||||
const idx_val = frame.getRef(fr.index);
|
||||
const idx: usize = @intCast(switch (idx_val) {
|
||||
.int => |i| i,
|
||||
else => return error.CannotEvalComptime,
|
||||
else => return bailDetail("comptime field_value(s, i): index operand is not an int"),
|
||||
});
|
||||
switch (base_val) {
|
||||
.aggregate => |agg| {
|
||||
@@ -1206,7 +1230,7 @@ pub const Interpreter = struct {
|
||||
.@"struct" => |s| s.fields,
|
||||
.@"union" => |u| u.fields,
|
||||
.tagged_union => |u| u.fields,
|
||||
else => return error.CannotEvalComptime,
|
||||
else => return bailDetail("comptime field_value(s, i): s's type is not a struct/union/tagged_union"),
|
||||
};
|
||||
const field_ty_tag: i64 = if (idx < fields.len) @intFromEnum(fields[idx].ty) else 0;
|
||||
const boxed = self.alloc.alloc(Value, 2) catch return error.CannotEvalComptime;
|
||||
@@ -1214,7 +1238,7 @@ pub const Interpreter = struct {
|
||||
boxed[1] = .{ .int = field_ty_tag };
|
||||
return .{ .value = .{ .aggregate = boxed } };
|
||||
},
|
||||
else => return error.CannotEvalComptime,
|
||||
else => return bailDetail("comptime field_value(s, i): s is not an aggregate Value (struct values must be materialized as aggregates at comptime)"),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1235,7 +1259,7 @@ pub const Interpreter = struct {
|
||||
return .{ .value = self.defaultContextValue() };
|
||||
}
|
||||
}
|
||||
return error.CannotEvalComptime;
|
||||
return bailDetail("comptime global_addr: only `&__sx_default_context` is currently materialised at comptime");
|
||||
},
|
||||
.func_ref => |fid| {
|
||||
return .{ .value = .{ .func_ref = fid } };
|
||||
@@ -1331,10 +1355,10 @@ pub const Interpreter = struct {
|
||||
slice[1] = .{ .int = @intCast(fields.len) };
|
||||
return .{ .value = .{ .aggregate = slice } };
|
||||
},
|
||||
else => return error.CannotEvalComptime,
|
||||
else => return bailDetail("comptime array_to_slice: slot-backed value is not an aggregate"),
|
||||
}
|
||||
},
|
||||
else => return error.CannotEvalComptime,
|
||||
else => return bailDetail("comptime array_to_slice: operand is neither an aggregate nor a slot pointer"),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1355,7 +1379,7 @@ pub const Interpreter = struct {
|
||||
const result = try self.call(fid, args);
|
||||
return .{ .value = result };
|
||||
},
|
||||
else => return error.CannotEvalComptime,
|
||||
else => return bailDetail("comptime call_indirect: callee is not a func_ref Value (raw fn-pointers from foreign calls aren't dispatchable in interp)"),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1366,9 +1390,13 @@ pub const Interpreter = struct {
|
||||
.placeholder => return .{ .value = .undef },
|
||||
|
||||
// ── Not yet evaluable at comptime ──────────────────
|
||||
.call_closure, .closure_create, .union_get, .union_gep, .vec_splat, .vec_extract, .vec_insert => {
|
||||
return error.CannotEvalComptime;
|
||||
},
|
||||
.call_closure => return bailDetail("comptime call_closure not yet implemented (closure trampoline ABI threading required)"),
|
||||
.closure_create => return bailDetail("comptime closure_create not yet implemented"),
|
||||
.union_get => return bailDetail("comptime union_get not yet implemented"),
|
||||
.union_gep => return bailDetail("comptime union_gep not yet implemented"),
|
||||
.vec_splat => return bailDetail("comptime vec_splat not yet implemented"),
|
||||
.vec_extract => return bailDetail("comptime vec_extract not yet implemented"),
|
||||
.vec_insert => return bailDetail("comptime vec_insert not yet implemented"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1407,7 +1435,7 @@ pub const Interpreter = struct {
|
||||
}
|
||||
}
|
||||
|
||||
return error.TypeError;
|
||||
return typeErrorDetail("comptime arithmetic: operand pair is neither both-int nor both-float-coercible");
|
||||
}
|
||||
|
||||
// ── Comparison helpers ──────────────────────────────────────────
|
||||
@@ -1458,7 +1486,7 @@ pub const Interpreter = struct {
|
||||
}
|
||||
}
|
||||
|
||||
return error.TypeError;
|
||||
return typeErrorDetail("comptime comparison: operand pair has no shared comparable shape (int/float/bool/string)");
|
||||
}
|
||||
|
||||
// ── Slot chain resolution ────────────────────────────────────
|
||||
@@ -1697,9 +1725,10 @@ pub const Interpreter = struct {
|
||||
const f = val.asFloat() orelse return error.TypeError;
|
||||
return .{ .value = .{ .float = @floor(f) } };
|
||||
},
|
||||
.cast, .type_of, .alloc, .dealloc => {
|
||||
return error.CannotEvalComptime;
|
||||
},
|
||||
.cast => return bailDetail("comptime #builtin cast: handled at lowering, not the interp (you reached this if a #builtin cast leaked into IR)"),
|
||||
.type_of => return bailDetail("comptime #builtin type_of: handled at lowering, not the interp"),
|
||||
.alloc => return bailDetail("comptime #builtin alloc unused (use context.allocator.alloc)"),
|
||||
.dealloc => return bailDetail("comptime #builtin dealloc unused (use context.allocator.dealloc)"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user