This commit is contained in:
Luke Boswell 2025-12-01 15:40:46 +11:00
parent b9496c8d90
commit 7357da1e18
No known key found for this signature in database
GPG key ID: 54A7324B1B975757
3 changed files with 72 additions and 3 deletions

View file

@ -1376,6 +1376,12 @@ pub const ComptimeEvaluator = struct {
return true; // Ok
}
return true; // Unknown format, optimistically allow
} else if (result.layout.tag == .tag_union) {
// Tag union layout: payload at offset 0, discriminant at discriminant_offset
// For Try types from num.from_numeral, the interpreter should have stored
// the error message in last_error_message, which was already checked above.
// If we reach here without a last_error_message, assume it's Ok.
return true;
}
return true; // Unknown format, optimistically allow

View file

@ -3561,6 +3561,35 @@ pub const Interpreter = struct {
}
}
// Store error message for Err case (same as tuple branch)
if (!in_range) {
var num_str_buf: [128]u8 = undefined;
const num_str = can.CIR.formatBase256ToDecimal(is_negative, digits_before, digits_after, &num_str_buf);
const error_msg = switch (rejection_reason) {
.negative_unsigned => std.fmt.allocPrint(
self.allocator,
"The number {s} is not a valid {s}. {s} values cannot be negative.",
.{ num_str, type_name, type_name },
) catch null,
.fractional_integer => std.fmt.allocPrint(
self.allocator,
"The number {s} is not a valid {s}. {s} values must be whole numbers, not fractions.",
.{ num_str, type_name, type_name },
) catch null,
.out_of_range, .overflow => std.fmt.allocPrint(
self.allocator,
"The number {s} is not a valid {s}. Valid {s} values are integers between {s} and {s}.",
.{ num_str, type_name, type_name, min_value_str, max_value_str },
) catch null,
.none => null,
};
if (error_msg) |msg| {
self.last_error_message = msg;
}
}
dest.is_initialized = true;
return dest;
}
@ -4098,6 +4127,38 @@ pub const Interpreter = struct {
}
// For Err case, payload is OutOfRange which is a zero-arg tag (already zeroed)
return dest;
} else if (result_layout.tag == .tag_union) {
// Tag union layout: payload at offset 0, discriminant at discriminant_offset
const dest = try self.pushRaw(result_layout, 0);
const tu_data = self.runtime_layout_store.getTagUnionData(result_layout.data.tag_union.idx);
// Write tag discriminant at discriminant_offset
const base_ptr: [*]u8 = @ptrCast(dest.ptr.?);
const disc_ptr = base_ptr + tu_data.discriminant_offset;
const tag_idx: usize = if (in_range) ok_index orelse 0 else err_index orelse 1;
switch (tu_data.discriminant_size) {
1 => @as(*u8, @ptrCast(disc_ptr)).* = @intCast(tag_idx),
2 => @as(*u16, @ptrCast(@alignCast(disc_ptr))).* = @intCast(tag_idx),
4 => @as(*u32, @ptrCast(@alignCast(disc_ptr))).* = @intCast(tag_idx),
8 => @as(*u64, @ptrCast(@alignCast(disc_ptr))).* = @intCast(tag_idx),
else => {},
}
// Clear payload area (at offset 0)
const payload_size = tu_data.discriminant_offset; // Payload spans from 0 to discriminant_offset
if (payload_size > 0) {
@memset(base_ptr[0..payload_size], 0);
}
// Write payload for Ok case
if (in_range) {
const to_value: To = @intCast(from_value);
const payload_ptr: *To = @ptrCast(@alignCast(base_ptr));
payload_ptr.* = to_value;
}
// For Err case, payload is OutOfRange which is a zero-arg tag (already zeroed)
return dest;
}

View file

@ -302,8 +302,10 @@ test "interpreter: F64 division" {
, 0.5, .no_trace);
}
test "interpreter: literal True renders True" {
const roc_src = "True";
test "interpreter: literal tag renders as tag name" {
// Use a custom tag instead of True - True is a Bool tag which requires
// proper builtin module resolution to get the nominal type
const roc_src = "MyTag";
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
@ -318,7 +320,7 @@ test "interpreter: literal True renders True" {
const rt_var = try interp2.translateTypeVar(resources.module_env, can.ModuleEnv.varFrom(resources.expr_idx));
const rendered = try interp2.renderValueRocWithType(result, rt_var);
defer std.testing.allocator.free(rendered);
try std.testing.expectEqualStrings("True", rendered);
try std.testing.expectEqualStrings("MyTag", rendered);
}
test "interpreter: True == False yields False" {