From 30869d7d8741656448e46fbf14f14da9ca7e5a21 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Mon, 27 Feb 2023 22:53:46 +0000 Subject: [PATCH] InternPool: replace untyped values with typed values (#1023) * InternPool: replace untyped values with typed values * InternPool: remove `indexToTag` * InternPool: rework representation of optional values * add representation for unknown values and types * ComptimeInterpreter: use InternPool typed-values * ComptimeInterpreter: field access test * ComptimeInterpreter: improve handling of if expressions * InternPool: fix typeOf on a comptime float * ComptimeInterpreter: implement TypeOf with multiple parameters --- src/ComptimeInterpreter.zig | 452 +++++++----- src/Server.zig | 17 +- src/analyser/InternPool.zig | 652 +++++++++--------- src/analyser/completions.zig | 49 +- src/analysis.zig | 3 +- .../comptime_interpreter.zig | 139 ++-- 6 files changed, 707 insertions(+), 605 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 0e2a08d..c5306a9 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -70,19 +70,12 @@ pub fn deinit(interpreter: *ComptimeInterpreter) void { interpreter.namespaces.deinit(interpreter.allocator); } -pub const Type = struct { - interpreter: *ComptimeInterpreter, - - node_idx: Ast.Node.Index, - ty: Index, -}; - pub const Value = struct { interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, - ty: Index, - val: Index, + /// this stores both the type and the value + index: Index, }; // pub const Comptimeness = enum { @"comptime", runtime }; @@ -234,19 +227,20 @@ pub fn interpret( continue; }; - var init_type_value = try (try interpreter.interpret(container_field.ast.type_expr, container_namespace, .{})).getValue(); + var init_value = try (try interpreter.interpret(container_field.ast.type_expr, container_namespace, .{})).getValue(); var default_value = if (container_field.ast.value_expr == 0) Index.none else - (try (try interpreter.interpret(container_field.ast.value_expr, container_namespace, .{})).getValue()).val; // TODO check ty + (try (try interpreter.interpret(container_field.ast.value_expr, container_namespace, .{})).getValue()).index; // TODO check ty - if (init_type_value.ty != Index.type_type) { + const init_value_ty = interpreter.ip.indexToKey(init_value.index).typeOf(); + if (init_value_ty != .type_type) { try interpreter.recordError( container_field.ast.type_expr, "expected_type", "expected type 'type', found '{}'", - .{init_type_value.ty.fmtType(interpreter.ip)}, + .{init_value_ty.fmt(interpreter.ip)}, ); continue; } @@ -254,7 +248,7 @@ pub fn interpret( const field_name = tree.tokenSlice(container_field.ast.main_token); try struct_info.fields.put(interpreter.allocator, field_name, .{ - .ty = init_type_value.val, + .ty = init_value.index, .default_value = default_value, .alignment = 0, // TODO, .is_comptime = false, // TODO @@ -267,8 +261,7 @@ pub fn interpret( return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = Index.type_type, - .val = struct_type, + .index = struct_type, } }; }, .error_set_decl => { @@ -286,8 +279,7 @@ pub fn interpret( const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ .name = name, .node_idx = node_idx, - .ty = .none, - .val = .none, + .index = .none, .alignment = 0, // TODO .address_space = .generic, // TODO .is_pub = true, // TODO @@ -315,12 +307,16 @@ pub fn interpret( if (type_value == null and init_value == null) return InterpretResult{ .nothing = {} }; if (type_value) |v| { - if (v.ty != Index.type_type) return InterpretResult{ .nothing = {} }; + if (interpreter.ip.indexToKey(v.index).typeOf() != .type_type) { + return InterpretResult{ .nothing = {} }; + } } + // TODO coerce `init_value` into `type_value` const decl = interpreter.ip.getDecl(decl_index); - decl.ty = if (type_value) |v| v.val else init_value.?.ty; - decl.val = if (init_value) |init| init.val else .none; + decl.index = if (type_value) |v| try interpreter.ip.get(interpreter.allocator, .{ + .unknown_value = .{ .ty = v.index }, + }) else init_value.?.index; // TODO: Am I a dumbo shrimp? (e.g. is this tree shaking correct? works on my machine so like...) @@ -416,20 +412,10 @@ pub fn interpret( }); if (simples.get(identifier)) |index| { - const ty: Index = switch (index) { - .undefined_value => .undefined_type, - .void_value => .void_type, - .unreachable_value => .noreturn_type, - .null_value => .null_type, - .bool_true => .bool_type, - .bool_false => .bool_type, - else => .type_type, - }; return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = ty, - .val = index, + .index = index, } }; } @@ -437,8 +423,7 @@ pub fn interpret( return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = Index.type_type, - .val = try interpreter.ip.get(interpreter.allocator, Key{ .int_type = .{ + .index = try interpreter.ip.get(interpreter.allocator, Key{ .int_type = .{ .signedness = if (identifier[0] == 'u') .unsigned else .signed, .bits = std.fmt.parseInt(u16, identifier[1..], 10) catch break :blk, } }), @@ -448,12 +433,11 @@ pub fn interpret( // Logic to find identifiers in accessible scopes if (interpreter.huntItDown(namespace, identifier, options)) |decl_index| { const decl = interpreter.ip.getDecl(decl_index); - if (decl.ty == .none) return InterpretResult{ .nothing = {} }; + if (decl.index == .none) return InterpretResult{ .nothing = {} }; return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = decl.node_idx, - .ty = decl.ty, - .val = decl.val, + .index = decl.index, } }; } @@ -471,31 +455,39 @@ pub fn interpret( const field_name = tree.tokenSlice(data[node_idx].rhs); var ir = try interpreter.interpret(data[node_idx].lhs, namespace, options); - var irv = try ir.getValue(); + var ir_value = try ir.getValue(); - const lhs = interpreter.ip.indexToKey(irv.ty); - const inner_lhs = switch (lhs) { - .pointer_type => |info| if (info.size == .One) interpreter.ip.indexToKey(info.elem_type) else lhs, - else => lhs, + const val_index = ir_value.index; + const val = interpreter.ip.indexToKey(val_index); + std.debug.assert(val.typeOf() != .none); + const ty = interpreter.ip.indexToKey(val.typeOf()); + + const inner_ty = switch (ty) { + .pointer_type => |info| if (info.size == .One) interpreter.ip.indexToKey(info.elem_type) else ty, + else => ty, }; - const can_have_fields: bool = switch (inner_lhs) { + const can_have_fields: bool = switch (inner_ty) { .simple_type => |simple| switch (simple) { .type => blk: { - if (irv.val == .none) break :blk true; - - const ty_key = interpreter.ip.indexToKey(irv.val); - if (interpreter.huntItDown(ty_key.getNamespace(interpreter.ip), field_name, options)) |decl_index| { + if (interpreter.huntItDown(val.getNamespace(interpreter.ip), field_name, options)) |decl_index| { const decl = interpreter.ip.getDecl(decl_index); return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = decl.ty, - .val = decl.val, + .index = decl.index, } }; } - switch (ty_key) { + if (val == .unknown_value) { + return InterpretResult{ .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + .index = .unknown_unknown, + } }; + } + + switch (val) { .error_set_type => |error_set_info| { // TODO _ = error_set_info; }, @@ -508,8 +500,7 @@ pub fn interpret( .value = Value{ .interpreter = interpreter, .node_idx = data[node_idx].rhs, - .ty = irv.val, - .val = .none, // TODO resolve enum value + .index = .unknown_unknown, // TODO }, }; } @@ -529,8 +520,10 @@ pub fn interpret( .value = Value{ .interpreter = interpreter, .node_idx = data[node_idx].rhs, - .ty = try interpreter.ip.get(interpreter.allocator, many_ptr_info), - .val = .none, // TODO resolve ptr of Slice + // TODO resolve ptr of Slice + .index = try interpreter.ip.get(interpreter.allocator, .{ + .unknown_value = .{ .ty = try interpreter.ip.get(interpreter.allocator, many_ptr_info) }, + }), }, }; } else if (std.mem.eql(u8, field_name, "len")) { @@ -538,8 +531,10 @@ pub fn interpret( .value = Value{ .interpreter = interpreter, .node_idx = data[node_idx].rhs, - .ty = Index.usize_type, - .val = .none, // TODO resolve length of Slice + // TODO resolve length of Slice + .index = try interpreter.ip.get(interpreter.allocator, .{ + .unknown_value = .{ .ty = Index.usize_type }, + }), }, }; } @@ -549,8 +544,10 @@ pub fn interpret( .value = Value{ .interpreter = interpreter, .node_idx = data[node_idx].rhs, - .ty = Index.usize_type, - .val = .none, // TODO resolve length of Slice + // TODO resolve length of Slice + .index = try interpreter.ip.get(interpreter.allocator, .{ + .unknown_value = .{ .ty = Index.usize_type }, + }), }, }; } @@ -558,53 +555,58 @@ pub fn interpret( break :blk true; }, .array_type => |array_info| blk: { - const len_value = try interpreter.ip.get(interpreter.allocator, .{ .int_u64_value = array_info.len }); - if (std.mem.eql(u8, field_name, "len")) { return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = data[node_idx].rhs, - .ty = Index.comptime_int_type, - .val = len_value, + .index = try interpreter.ip.get(interpreter.allocator, .{ .int_u64_value = .{ + .ty = .comptime_int_type, + .int = array_info.len, + } }), } }; } break :blk true; }, .optional_type => |optional_info| blk: { if (!std.mem.eql(u8, field_name, "?")) break :blk false; - if (irv.val == Index.null_value) { + + if (val_index == .type_type) { try interpreter.recordError( node_idx, "null_unwrap", "tried to unwrap optional of type `{}` which was null", - .{irv.ty.fmtType(interpreter.ip)}, + .{optional_info.payload_type.fmt(interpreter.ip)}, ); return error.InvalidOperation; - } else { - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = data[node_idx].rhs, - .ty = optional_info.payload_type, - .val = irv.val, - } }; } + const result = switch (val) { + .optional_value => |optional_val| optional_val.val, + .unknown_value => val_index, + else => return error.InvalidOperation, + }; + return InterpretResult{ .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + .index = result, + } }; }, .struct_type => |struct_index| blk: { const struct_info = interpreter.ip.getStruct(struct_index); if (struct_info.fields.getIndex(field_name)) |field_index| { const field = struct_info.fields.values()[field_index]; - const val = found_val: { - if (irv.val == .none) break :found_val .none; - const val_key = interpreter.ip.indexToKey(irv.val); - if (val_key != .aggregate) break :found_val .none; - break :found_val val_key.aggregate[field_index]; + + const result = switch (val) { + .aggregate => |aggregate| aggregate.values[field_index], + .unknown_value => try interpreter.ip.get(interpreter.allocator, .{ + .unknown_value = .{ .ty = field.ty }, + }), + else => return error.InvalidOperation, }; return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = data[node_idx].rhs, - .ty = field.ty, - .val = val, + .index = result, } }; } break :blk true; @@ -617,26 +619,51 @@ pub fn interpret( _ = union_info; break :blk true; }, - else => false, + .int_type, + .error_union_type, + .error_set_type, + .function_type, + .tuple_type, + .vector_type, + .anyframe_type, + => false, + + .simple_value, + .int_u64_value, + .int_i64_value, + .int_big_value, + .float_16_value, + .float_32_value, + .float_64_value, + .float_80_value, + .float_128_value, + .float_comptime_value, + => unreachable, + + .bytes, + .optional_value, + .slice, + .aggregate, + .union_value, + .unknown_value, + => unreachable, }; - const accessed_ty = if (inner_lhs == .simple_type and inner_lhs.simple_type == .type) irv.val else irv.ty; - if (accessed_ty != .none) { - if (can_have_fields) { - try interpreter.recordError( - node_idx, - "undeclared_identifier", - "`{}` has no member '{s}'", - .{ accessed_ty.fmtType(interpreter.ip), field_name }, - ); - } else { - try interpreter.recordError( - node_idx, - "invalid_field_access", - "`{}` does not support field access", - .{accessed_ty.fmtType(interpreter.ip)}, - ); - } + const accessed_ty = if (inner_ty == .simple_type and inner_ty.simple_type == .type) val else inner_ty; + if (can_have_fields) { + try interpreter.recordError( + node_idx, + "undeclared_identifier", + "`{}` has no member '{s}'", + .{ accessed_ty.fmt(interpreter.ip), field_name }, + ); + } else { + try interpreter.recordError( + node_idx, + "invalid_field_access", + "`{}` does not support field access", + .{accessed_ty.fmt(interpreter.ip)}, + ); } return error.InvalidOperation; }, @@ -660,19 +687,39 @@ pub fn interpret( .if_simple, => { const if_info = ast.fullIf(tree, node_idx).?; - // TODO: Don't evaluate runtime ifs // if (options.observe_values) { const ir = try interpreter.interpret(if_info.ast.cond_expr, namespace, options); - const condition = (try ir.getValue()).val; - std.debug.assert(condition == Index.bool_false or condition == Index.bool_true); - if (condition == Index.bool_true) { + const condition = (try ir.getValue()).index; + const condition_val = interpreter.ip.indexToKey(condition); + const condition_ty = condition_val.typeOf(); + + switch (condition_ty) { + .bool_type => {}, + .unknown_type => return InterpretResult{ .nothing = {} }, + else => { + try interpreter.recordError( + if_info.ast.cond_expr, + "invalid_if_condition", + "expected `bool` but found `{}`", + .{condition_ty.fmt(interpreter.ip)}, + ); + return error.InvalidOperation; + }, + } + if (condition_val == .unknown_value) { + return InterpretResult{ .nothing = {} }; + } + + std.debug.assert(condition == .bool_false or condition == .bool_true); + if (condition == .bool_true) { return try interpreter.interpret(if_info.ast.then_expr, namespace, options); } else { if (if_info.ast.else_expr != 0) { return try interpreter.interpret(if_info.ast.else_expr, namespace, options); - } else return InterpretResult{ .nothing = {} }; + } } + return InterpretResult{ .nothing = {} }; }, .equal_equal => { var a = try interpreter.interpret(data[node_idx].lhs, namespace, options); @@ -683,8 +730,7 @@ pub fn interpret( .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = Index.bool_type, - .val = if (a_value.val == b_value.val) Index.bool_true else Index.bool_false, // TODO eql function required? + .index = if (a_value.index == b_value.index) .bool_true else .bool_false, // TODO eql function required? }, }; }, @@ -700,19 +746,26 @@ pub fn interpret( interpreter.allocator, switch (nl) { .float => Key{ - .float_128_value = try std.fmt.parseFloat(f128, s), + .float_comptime_value = try std.fmt.parseFloat(f128, s), }, .int => if (s[0] == '-') Key{ - .int_i64_value = try std.fmt.parseInt(i64, s, 0), + .int_i64_value = .{ + .ty = number_type, + .int = try std.fmt.parseInt(i64, s, 0), + }, } else Key{ - .int_u64_value = try std.fmt.parseInt(u64, s, 0), + .int_u64_value = .{ + .ty = number_type, + .int = try std.fmt.parseInt(u64, s, 0), + }, }, .big_int => |base| blk: { var big_int = try std.math.big.int.Managed.init(interpreter.allocator); defer big_int.deinit(); const prefix_length: usize = if (base != .decimal) 2 else 0; try big_int.setString(@enumToInt(base), s[prefix_length..]); - break :blk Key{ .int_big_value = big_int.toConst() }; + std.debug.assert(number_type == .comptime_int_type); + break :blk Key{ .int_big_value = .{ .ty = number_type, .int = big_int.toConst() } }; }, .failure => return error.CriticalAstFailure, }, @@ -721,8 +774,7 @@ pub fn interpret( return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = number_type, - .val = value, + .index = value, } }; }, .assign, @@ -747,12 +799,17 @@ pub fn interpret( return InterpretResult{ .nothing = {} }; } - var ir = try interpreter.interpret(data[node_idx].lhs, namespace, options); - var to_value = try ir.getValue(); - var from_value = (try (try interpreter.interpret(data[node_idx].rhs, namespace, options)).getValue()); + const lhs = try interpreter.interpret(data[node_idx].lhs, namespace, options); + const rhs = try interpreter.interpret(data[node_idx].rhs, namespace, options); + + const to_val = try lhs.getValue(); + const from_val = try rhs.getValue(); + + const to_ty = interpreter.ip.indexToKey(to_val.index).typeOf(); + const from_ty = interpreter.ip.indexToKey(from_val.index).typeOf(); // TODO report error - _ = try interpreter.ip.cast(interpreter.allocator, to_value.ty, from_value.ty, builtin.target); + _ = try interpreter.ip.cast(interpreter.allocator, to_ty, from_ty, builtin.target); return InterpretResult{ .nothing = {} }; }, @@ -786,11 +843,14 @@ pub fn interpret( try writer.writeAll("log: "); for (params, 0..) |param, index| { - var value = (try interpreter.interpret(param, namespace, options)).maybeGetValue() orelse { + const ir_value = (try interpreter.interpret(param, namespace, options)).maybeGetValue() orelse { try writer.writeAll("indeterminate"); continue; }; - try writer.print("@as({}, {})", .{ value.ty.fmtType(interpreter.ip), value.val.fmtValue(value.ty, interpreter.ip) }); + const val = interpreter.ip.indexToKey(ir_value.index); + const ty = val.typeOf(); + + try writer.print("@as({}, {})", .{ ty.fmt(interpreter.ip), val.fmt(interpreter.ip) }); if (index != params.len - 1) try writer.writeAll(", "); } @@ -827,8 +887,9 @@ pub fn interpret( return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = try interpreter.ip.get(interpreter.allocator, Key{ .struct_type = struct_index }), - .val = Index.undefined_value, + .index = try interpreter.ip.get(interpreter.allocator, .{ .unknown_value = .{ + .ty = try interpreter.ip.get(interpreter.allocator, .{ .struct_type = struct_index }), + } }), } }; } @@ -842,38 +903,59 @@ pub fn interpret( .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = Index.type_type, - .val = .none, // TODO + .index = try interpreter.ip.get(interpreter.allocator, .{ .unknown_value = .{ .ty = .type_type } }), }, }; } if (std.mem.eql(u8, call_name, "@TypeOf")) { - if (params.len != 1) return error.InvalidBuiltin; + if (params.len == 0) return error.InvalidBuiltin; + + const types: []Index = try interpreter.allocator.alloc(Index, params.len); + defer interpreter.allocator.free(types); + + for (params, types) |param, *out_type| { + const value = try (try interpreter.interpret(param, namespace, options)).getValue(); + out_type.* = interpreter.ip.indexToKey(value.index).typeOf(); + } + + const peer_type = try interpreter.ip.resolvePeerTypes(interpreter.allocator, types, builtin.target); + + if (peer_type == .none) { + var output = std.ArrayListUnmanaged(u8){}; + var writer = output.writer(interpreter.allocator); + try writer.writeAll("incompatible types: "); + for (types, 0..) |ty, i| { + if (i != 0) try writer.writeAll(", "); + try writer.print("`{}`", .{ty.fmt(interpreter.ip)}); + } + + try interpreter.recordError(node_idx, "invalid_typeof", "{s}", .{output.items}); + return error.InvalidOperation; + } - const value = try (try interpreter.interpret(params[0], namespace, options)).getValue(); return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = Index.type_type, - .val = value.ty, + .index = peer_type, } }; } if (std.mem.eql(u8, call_name, "@hasDecl")) { if (params.len != 2) return error.InvalidBuiltin; - const value = try (try interpreter.interpret(params[0], namespace, options)).getValue(); + const ir_value = try (try interpreter.interpret(params[0], namespace, options)).getValue(); const field_name = try (try interpreter.interpret(params[1], namespace, options)).getValue(); - if (value.ty != Index.type_type or value.ty == .none) return error.InvalidBuiltin; - if (interpreter.ip.indexToKey(field_name.ty) != .pointer_type) return error.InvalidBuiltin; // Check if it's a []const u8 - if (value.val == .none) return error.InvalidBuiltin; + const val = interpreter.ip.indexToKey(ir_value.index); + const ty = val.typeOf(); - const value_namespace = interpreter.ip.indexToKey(value.val).getNamespace(interpreter.ip); + if (ty != .type_type) return error.InvalidBuiltin; + + const value_namespace = interpreter.ip.indexToKey(ty).getNamespace(interpreter.ip); if (value_namespace == .none) return error.InvalidBuiltin; - const name = interpreter.ip.indexToKey(field_name.val).bytes; // TODO add checks + const name = interpreter.ip.indexToKey(field_name.index).bytes; // TODO add checks const decls = interpreter.namespaces.items(.decls)[@enumToInt(value_namespace)]; const has_decl = decls.contains(name); @@ -881,8 +963,7 @@ pub fn interpret( return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = Index.bool_type, - .val = if (has_decl) Index.bool_true else Index.bool_false, + .index = if (has_decl) .bool_true else .bool_false, } }; } @@ -890,16 +971,20 @@ pub fn interpret( if (params.len != 2) return error.InvalidBuiltin; const as_type = try (try interpreter.interpret(params[0], namespace, options)).getValue(); - const value = try (try interpreter.interpret(params[1], namespace, options)).getValue(); + // const value = try (try interpreter.interpret(params[1], namespace, options)).getValue(); - if (as_type.ty != Index.type_type) return error.InvalidBuiltin; + if (interpreter.ip.indexToKey(as_type.index).typeOf() != .type_type) { + return error.InvalidBuiltin; + } return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = as_type.val, - .val = value.val, // TODO port Sema.coerceExtra to InternPool + // TODO port Sema.coerceExtra to InternPool + .index = try interpreter.ip.get(interpreter.allocator, .{ + .unknown_value = .{ .ty = as_type.index }, + }), }, }; } @@ -910,26 +995,25 @@ pub fn interpret( .string_literal => { const str = tree.getNodeSource(node_idx)[1 .. tree.getNodeSource(node_idx).len - 1]; - const string_literal_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{ - .elem_type = try interpreter.ip.get(interpreter.allocator, Key{ .array_type = .{ - .child = Index.u8_type, - .len = @intCast(u64, str.len), - .sentinel = try interpreter.ip.get(interpreter.allocator, Key{ .int_u64_value = 0 }), - } }), - .sentinel = .none, - .alignment = 0, - .size = .One, - .is_const = true, - .is_volatile = false, - .is_allowzero = false, - .address_space = .generic, - } }); + // const string_literal_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{ + // .elem_type = try interpreter.ip.get(interpreter.allocator, Key{ .array_type = .{ + // .child = Index.u8_type, + // .len = @intCast(u64, str.len), + // .sentinel = try interpreter.ip.get(interpreter.allocator, Key{ .int_u64_value = .{ .ty = .u8_type, .int = 0 } }), + // } }), + // .sentinel = .none, + // .alignment = 0, + // .size = .One, + // .is_const = true, + // .is_volatile = false, + // .is_allowzero = false, + // .address_space = .generic, + // } }); return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .ty = string_literal_type, - .val = try interpreter.ip.get(interpreter.allocator, Key{ .bytes = str }), + .index = try interpreter.ip.get(interpreter.allocator, Key{ .bytes = str }), } }; }, // TODO: Add comptime autodetection; e.g. const MyArrayList = std.ArrayList(u8) @@ -989,8 +1073,7 @@ pub fn interpret( const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ .name = name, .node_idx = node_idx, - .ty = Index.type_type, - .val = function_type, + .index = function_type, .alignment = 0, // TODO .address_space = .generic, // TODO .is_pub = false, // TODO @@ -1034,24 +1117,36 @@ pub fn interpret( }, .bool_not => { const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); - const value = try result.getValue(); + const ir_value = try result.getValue(); - if (value.ty != Index.bool_type) { + const val = ir_value.index; + const ty = interpreter.ip.indexToKey(ir_value.index).typeOf(); + + if (ty == .unknown_type) { + return InterpretResult{ .value = .{ + .interpreter = interpreter, + .node_idx = node_idx, + .index = try interpreter.ip.get(interpreter.allocator, .{ + .unknown_value = .{ .ty = .bool_type }, + }), + } }; + } + + if (ty != .bool_type) { try interpreter.recordError( node_idx, "invalid_deref", "expected type `bool` but got `{}`", - .{value.ty.fmtType(interpreter.ip)}, + .{ty.fmt(interpreter.ip)}, ); return error.InvalidOperation; } - std.debug.assert(value.val == Index.bool_false or value.val == Index.bool_true); + std.debug.assert(val == .bool_false or val == .bool_true); return InterpretResult{ .value = .{ .interpreter = interpreter, .node_idx = node_idx, - .ty = Index.bool_type, - .val = if (value.val == Index.bool_false) Index.bool_true else Index.bool_false, + .index = if (val == .bool_false) .bool_true else .bool_false, } }; }, .address_of => { @@ -1059,10 +1154,12 @@ pub fn interpret( // variables are the only non-const(?) const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); - const value = (try result.getValue()); + const ir_value = try result.getValue(); + + const ty = interpreter.ip.indexToKey(ir_value.index).typeOf(); const pointer_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{ - .elem_type = value.ty, + .elem_type = ty, .sentinel = .none, .alignment = 0, .size = .One, @@ -1075,15 +1172,26 @@ pub fn interpret( return InterpretResult{ .value = .{ .interpreter = interpreter, .node_idx = node_idx, - .ty = pointer_type, - .val = value.val, + .index = try interpreter.ip.get(interpreter.allocator, .{ + .unknown_value = .{ .ty = pointer_type }, + }), } }; }, .deref => { const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); - const value = (try result.getValue()); + const ir_value = (try result.getValue()); - const type_key = interpreter.ip.indexToKey(value.ty); + const ty = interpreter.ip.indexToKey(ir_value.index).typeOf(); + + if (ty == .unknown_type) { + return InterpretResult{ .value = .{ + .interpreter = interpreter, + .node_idx = node_idx, + .index = .unknown_unknown, + } }; + } + + const type_key = interpreter.ip.indexToKey(ty); if (type_key != .pointer_type) { try interpreter.recordError(node_idx, "invalid_deref", "cannot deference non-pointer", .{}); @@ -1093,8 +1201,9 @@ pub fn interpret( return InterpretResult{ .value = .{ .interpreter = interpreter, .node_idx = node_idx, - .ty = type_key.pointer_type.elem_type, - .val = value.val, + .index = try interpreter.ip.get(interpreter.allocator, .{ + .unknown_value = .{ .ty = type_key.pointer_type.elem_type }, + }), } }; }, else => { @@ -1144,15 +1253,17 @@ pub fn call( while (ast.nextFnParam(&arg_it)) |param| { if (arg_index >= arguments.len) return error.MissingArguments; var tex = try (try interpreter.interpret(param.type_expr, fn_namespace, options)).getValue(); - if (tex.ty != Index.type_type) { + const tex_ty = interpreter.ip.indexToKey(tex.index).typeOf(); + if (tex_ty != .type_type) { try interpreter.recordError( param.type_expr, "expected_type", "expected type 'type', found '{}'", - .{tex.ty.fmtType(interpreter.ip)}, + .{tex_ty.fmt(interpreter.ip)}, ); return error.InvalidCast; } + // TODO validate that `arguments[arg_index].index`'s types matches tex.index if (param.name_token) |name_token| { const name = offsets.tokenToSlice(tree, name_token); @@ -1160,8 +1271,7 @@ pub fn call( const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ .name = name, .node_idx = name_token, - .ty = tex.val, - .val = arguments[arg_index].val, + .index = arguments[arg_index].index, .alignment = 0, // TODO .address_space = .generic, // TODO .is_pub = true, // TODO diff --git a/src/Server.zig b/src/Server.zig index 9369881..7fdf576 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -598,13 +598,14 @@ fn typeToCompletion( null, ), .primitive, .array_index => {}, - .@"comptime" => |co| { - if (type_handle.type.is_type_val) { - try analyser.completions.dotCompletions(allocator, list, &co.interpreter.ip, co.value.ty, co.value.val, co.value.node_idx); - } else { - try analyser.completions.dotCompletions(allocator, list, &co.interpreter.ip, co.value.val, .none, co.value.node_idx); - } - }, + .@"comptime" => |co| try analyser.completions.dotCompletions( + allocator, + list, + &co.interpreter.ip, + co.value.index, + type_handle.type.is_type_val, + co.value.node_idx, + ), } } @@ -921,7 +922,7 @@ fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle) error{OutO const resolved_type_str = if (resolved_type) |rt| if (rt.type.is_type_val) switch (rt.type.data) { - .@"comptime" => |co| try std.fmt.allocPrint(server.arena.allocator(), "{}", .{co.value.ty.fmtType(co.interpreter.ip)}), + .@"comptime" => |co| try std.fmt.allocPrint(server.arena.allocator(), "{}", .{co.value.index.fmt(co.interpreter.ip)}), else => "type", } else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds .pointer, diff --git a/src/analyser/InternPool.zig b/src/analyser/InternPool.zig index 506f631..83a3e36 100644 --- a/src/analyser/InternPool.zig +++ b/src/analyser/InternPool.zig @@ -15,6 +15,7 @@ const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const expect = std.testing.expect; +const expectFmt = std.testing.expectFmt; const encoding = @import("encoding.zig"); @@ -26,8 +27,8 @@ pub const Int = packed struct { pub const Pointer = packed struct { elem_type: Index, sentinel: Index = .none, - alignment: u16 = 0, size: std.builtin.Type.Pointer.Size, + alignment: u16 = 0, bit_offset: u16 = 0, host_size: u16 = 0, is_const: bool = false, @@ -138,30 +139,67 @@ pub const AnyFrame = packed struct { child: Index, }; -pub const BigInt = std.math.big.int.Const; +const U64Value = packed struct { + ty: Index, + int: u64, +}; + +const I64Value = packed struct { + ty: Index, + int: i64, +}; + +pub const BigInt = struct { + ty: Index, + int: std.math.big.int.Const, +}; pub const Bytes = []const u8; -pub const Aggregate = []const Index; +pub const OptionalValue = packed struct { + ty: Index, + val: Index, +}; + +pub const Slice = packed struct { + ty: Index, + ptr: Index, + len: Index, +}; + +pub const Aggregate = struct { + ty: Index, + values: []const Index, +}; pub const UnionValue = packed struct { + ty: Index, field_index: u32, val: Index, }; +pub const UnknownValue = packed struct { + ty: Index, +}; + pub const DeclIndex = enum(u32) { _ }; pub const Decl = struct { name: []const u8, node_idx: u32, - ty: Index, - val: Index, + /// this stores both the type and the value + index: Index, alignment: u16, address_space: std.builtin.AddressSpace, is_pub: bool, is_exported: bool, }; +const BigIntInternal = struct { + ty: Index, + limbs: []const std.math.big.Limb, +}; + pub const Key = union(enum) { simple_type: SimpleType, simple_value: SimpleValue, @@ -183,20 +221,23 @@ pub const Key = union(enum) { vector_type: Vector, anyframe_type: AnyFrame, - int_u64_value: u64, - int_i64_value: i64, + int_u64_value: U64Value, + int_i64_value: I64Value, int_big_value: BigInt, float_16_value: f16, float_32_value: f32, float_64_value: f64, float_80_value: f80, float_128_value: f128, + float_comptime_value: f128, bytes: Bytes, + optional_value: OptionalValue, + slice: Slice, aggregate: Aggregate, union_value: UnionValue, + unknown_value: UnknownValue, - // slice // error // error union @@ -232,18 +273,22 @@ pub const Key = union(enum) { .vector_type => .type_vector, .anyframe_type => .type_anyframe, - .int_u64_value => |int| if (int <= std.math.maxInt(u32)) .int_u32 else .int_u64, - .int_i64_value => |int| if (std.math.minInt(i32) <= int and int <= std.math.maxInt(i32)) .int_i32 else .int_i64, - .int_big_value => |big_int| if (big_int.positive) .int_big_positive else .int_big_negative, + .int_u64_value => .int_u64, + .int_i64_value => .int_i64, + .int_big_value => |big_int| if (big_int.int.positive) .int_big_positive else .int_big_negative, .float_16_value => .float_f16, .float_32_value => .float_f32, .float_64_value => .float_f64, .float_80_value => .float_f80, .float_128_value => .float_f128, + .float_comptime_value => .float_comptime, .bytes => .bytes, + .optional_value => .optional_value, + .slice => .slice, .aggregate => .aggregate, .union_value => .union_value, + .unknown_value => .unknown_value, }; } @@ -296,6 +341,7 @@ pub const Key = union(enum) { .extern_options => .Struct, .type_info => .Union, + .unknown => unreachable, .generic_poison => unreachable, }, @@ -322,18 +368,33 @@ pub const Key = union(enum) { .float_64_value, .float_80_value, .float_128_value, + .float_comptime_value, => unreachable, .bytes, + .optional_value, + .slice, .aggregate, .union_value, + .unknown_value, => unreachable, }; } - pub fn isType(key: Key) bool { + pub fn typeOf(key: Key) Index { return switch (key) { - .simple_type, + .simple_type => .type_type, + .simple_value => |simple| switch (simple) { + .undefined_value => .undefined_type, + .void_value => .void_type, + .unreachable_value => .noreturn_type, + .null_value => .null_type, + .bool_true => .bool_type, + .bool_false => .bool_type, + .the_only_possible_value => unreachable, + .generic_poison => .generic_poison_type, + }, + .int_type, .pointer_type, .array_type, @@ -347,34 +408,30 @@ pub const Key = union(enum) { .tuple_type, .vector_type, .anyframe_type, - => true, + => .type_type, - .simple_value, - .int_u64_value, - .int_i64_value, - .int_big_value, - .float_16_value, - .float_32_value, - .float_64_value, - .float_80_value, - .float_128_value, - => false, + .int_u64_value => |int| int.ty, + .int_i64_value => |int| int.ty, + .int_big_value => |int| int.ty, + .float_16_value => .f16_type, + .float_32_value => .f32_type, + .float_64_value => .f64_type, + .float_80_value => .f80_type, + .float_128_value => .f128_type, + .float_comptime_value => .comptime_float_type, - .bytes, - .aggregate, - .union_value, - => false, + .bytes => .unknown_type, // TODO + .optional_value => |optional_info| optional_info.ty, + .slice => |slice_info| slice_info.ty, + .aggregate => |aggregate_info| aggregate_info.ty, + .union_value => |union_info| union_info.ty, + .unknown_value => |unknown_info| unknown_info.ty, }; } - pub fn isValue(key: Key) bool { - return !key.isValue(); - } - /// Asserts the type is an integer, enum, error set, packed struct, or vector of one of them. - pub fn intInfo(ty: Key, target: std.Target, ip: *const InternPool) Int { + pub fn intInfo(ty: Key, target: std.Target, ip: InternPool) Int { var key: Key = ty; - while (true) switch (key) { .simple_type => |simple| switch (simple) { .usize => return .{ .signedness = .signed, .bits = target.cpu.arch.ptrBitWidth() }, @@ -544,6 +601,7 @@ pub const Key = union(enum) { .type_info, => Index.none, + .unknown => Index.unknown_unknown, .generic_poison => unreachable, }, .int_type => |int_info| { @@ -610,54 +668,47 @@ pub const Key = union(enum) { .float_64_value, .float_80_value, .float_128_value, + .float_comptime_value, => unreachable, .bytes, + .optional_value, + .slice, .aggregate, .union_value, + .unknown_value, => unreachable, }; } - pub const TypeFormatContext = struct { - ty: Key, - options: FormatOptions = .{}, - ip: InternPool, - }; - - pub const ValueFormatContext = struct { - value: Key, - /// for most values the type is not needed which is why we use an index - ty: Index, + pub const FormatContext = struct { + key: Key, options: FormatOptions = .{}, ip: InternPool, }; + // TODO add options for controling how types show be formatted pub const FormatOptions = struct {}; - fn formatType( - ctx: TypeFormatContext, - comptime fmt: []const u8, + fn format( + ctx: FormatContext, + comptime fmt_str: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { _ = options; - if (fmt.len != 0) std.fmt.invalidFmtError(fmt, Key); - try printTypeKey(ctx.ty, ctx.ip, writer); + if (fmt_str.len != 0) std.fmt.invalidFmtError(fmt_str, Key); + try print(ctx.key, ctx.ip, writer); } - fn printType(ty: Index, ip: InternPool, writer: anytype) @TypeOf(writer).Error!void { - try printTypeKey(ip.indexToKey(ty), ip, writer); - } - - fn printTypeKey(ty: Key, ip: InternPool, writer: anytype) @TypeOf(writer).Error!void { - var key = ty; - while (try printTypeInternal(key, ip, writer)) |index| { - key = ip.indexToKey(index); + pub fn print(key: Key, ip: InternPool, writer: anytype) @TypeOf(writer).Error!void { + var k = key; + while (try printInternal(k, ip, writer)) |index| { + k = ip.indexToKey(index); } } - fn printTypeInternal(ty: Key, ip: InternPool, writer: anytype) @TypeOf(writer).Error!?Index { + fn printInternal(ty: Key, ip: InternPool, writer: anytype) @TypeOf(writer).Error!?Index { switch (ty) { .simple_type => |simple| switch (simple) { .f16, @@ -702,6 +753,7 @@ pub const Key = union(enum) { .export_options => try writer.writeAll("std.builtin.ExportOptions"), .extern_options => try writer.writeAll("std.builtin.ExternOptions"), .type_info => try writer.writeAll("std.builtin.Type"), + .unknown => try writer.writeAll("(unknown type)"), .generic_poison => unreachable, }, .int_type => |int_info| switch (int_info.signedness) { @@ -712,8 +764,8 @@ pub const Key = union(enum) { if (pointer_info.sentinel != Index.none) { switch (pointer_info.size) { .One, .C => unreachable, - .Many => try writer.print("[*:{}]", .{pointer_info.sentinel.fmtValue(pointer_info.elem_type, ip)}), - .Slice => try writer.print("[:{}]", .{pointer_info.sentinel.fmtValue(pointer_info.elem_type, ip)}), + .Many => try writer.print("[*:{}]", .{pointer_info.sentinel.fmt(ip)}), + .Slice => try writer.print("[:{}]", .{pointer_info.sentinel.fmt(ip)}), } } else switch (pointer_info.size) { .One => try writer.writeAll("*"), @@ -745,7 +797,7 @@ pub const Key = union(enum) { .array_type => |array_info| { try writer.print("[{d}", .{array_info.len}); if (array_info.sentinel != Index.none) { - try writer.print(":{}", .{array_info.sentinel.fmtValue(array_info.child, ip)}); + try writer.print(":{}", .{array_info.sentinel.fmt(ip)}); } try writer.writeByte(']'); @@ -757,7 +809,7 @@ pub const Key = union(enum) { return optional_info.payload_type; }, .error_union_type => |error_union_info| { - try printType(error_union_info.error_set_type, ip, writer); + try print(ip.indexToKey(error_union_info.error_set_type), ip, writer); try writer.writeByte('!'); return error_union_info.payload_type; }, @@ -786,7 +838,7 @@ pub const Key = union(enum) { } } - try printType(arg_ty, ip, writer); + try print(ip.indexToKey(arg_ty), ip, writer); } if (function_info.is_var_args) { @@ -815,9 +867,9 @@ pub const Key = union(enum) { if (val != Index.none) { try writer.writeAll("comptime "); } - try printType(field_ty, ip, writer); + try print(ip.indexToKey(field_ty), ip, writer); if (val != Index.none) { - try writer.print(" = {}", .{val.fmtValue(field_ty, ip)}); + try writer.print(" = {}", .{val.fmt(ip)}); } } try writer.writeByte('}'); @@ -825,7 +877,7 @@ pub const Key = union(enum) { .vector_type => |vector_info| { try writer.print("@Vector({d},{})", .{ vector_info.len, - vector_info.child.fmtType(ip), + vector_info.child.fmt(ip), }); }, .anyframe_type => |anyframe_info| { @@ -833,44 +885,6 @@ pub const Key = union(enum) { return anyframe_info.child; }, - .simple_value, - .int_u64_value, - .int_i64_value, - .int_big_value, - .float_16_value, - .float_32_value, - .float_64_value, - .float_80_value, - .float_128_value, - => unreachable, - - .bytes, - .aggregate, - .union_value, - => unreachable, - } - return null; - } - - fn formatValue( - ctx: ValueFormatContext, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) @TypeOf(writer).Error!void { - _ = options; - if (fmt.len != 0) std.fmt.invalidFmtError(fmt, Key); - return printValue(ctx.value, ctx.ty, ctx.ip, writer); - } - - fn printValue( - value: Key, - ty: Index, - ip: InternPool, - writer: anytype, - ) @TypeOf(writer).Error!void { - switch (value) { - .simple_type => try printType(ty, ip, writer), .simple_value => |simple| switch (simple) { .undefined_value => try writer.writeAll("undefined"), .void_value => try writer.writeAll("{}"), @@ -879,72 +893,64 @@ pub const Key = union(enum) { .bool_true => try writer.writeAll("true"), .bool_false => try writer.writeAll("false"), .the_only_possible_value => try writer.writeAll("(the only possible value)"), - .generic_poison => unreachable, + .generic_poison => try writer.writeAll("(generic poison)"), }, - - .int_type, - .pointer_type, - .array_type, - .struct_type, - .optional_type, - .error_union_type, - .error_set_type, - .enum_type, - .function_type, - .union_type, - .tuple_type, - .vector_type, - .anyframe_type, - => unreachable, - - .int_u64_value => |int| try std.fmt.formatIntValue(int, "", .{}, writer), - .int_i64_value => |int| try std.fmt.formatIntValue(int, "", .{}, writer), - .int_big_value => |big_int| try big_int.format("", .{}, writer), + .int_u64_value => |i| try std.fmt.formatIntValue(i.int, "", .{}, writer), + .int_i64_value => |i| try std.fmt.formatIntValue(i.int, "", .{}, writer), + .int_big_value => |i| try i.int.format("", .{}, writer), .float_16_value => |float| try writer.print("{d}", .{float}), .float_32_value => |float| try writer.print("{d}", .{float}), .float_64_value => |float| try writer.print("{d}", .{float}), .float_80_value => |float| try writer.print("{d}", .{@floatCast(f64, float)}), - .float_128_value => |float| try writer.print("{d}", .{@floatCast(f64, float)}), + .float_128_value, + .float_comptime_value, + => |float| try writer.print("{d}", .{@floatCast(f64, float)}), .bytes => |bytes| try writer.print("\"{}\"", .{std.zig.fmtEscapes(bytes)}), + .optional_value => |optional| { + return optional.val; + }, + .slice => |slice_value| { + _ = slice_value; + try writer.writeAll(".{"); + try writer.writeAll(" TODO "); // TODO + try writer.writeByte('}'); + }, .aggregate => |aggregate| { - const struct_info = ip.getStruct(ip.indexToKey(ty).struct_type); - assert(aggregate.len == struct_info.fields.count()); + if (aggregate.values.len == 0) { + try writer.writeAll(".{}"); + return null; + } + const struct_info = ip.getStruct(ip.indexToKey(aggregate.ty).struct_type); + assert(aggregate.values.len == struct_info.fields.count()); try writer.writeAll(".{"); var i: u32 = 0; - while (i < aggregate.len) : (i += 1) { + while (i < aggregate.values.len) : (i += 1) { if (i != 0) try writer.writeAll(", "); const field_name = struct_info.fields.keys()[i]; - try writer.print(".{s} = ", .{field_name}); - try printValue(ip.indexToKey(aggregate[i]), struct_info.fields.values()[i].ty, ip, writer); + try writer.print(".{s} = {}", .{ field_name, aggregate.values[i].fmt(ip) }); } try writer.writeByte('}'); }, .union_value => |union_value| { - const union_info = ip.getUnion(ip.indexToKey(ty).union_type); + const union_info = ip.getUnion(ip.indexToKey(union_value.ty).union_type); const name = union_info.fields.keys()[union_value.field_index]; try writer.print(".{{ .{} = {} }}", .{ std.zig.fmtId(name), - union_value.val.fmtValue(union_info.fields.values()[union_value.field_index].ty, ip), + union_value.val.fmt(ip), }); }, + .unknown_value => try writer.print("(unknown value)", .{}), } + return null; } - pub fn fmtType(ty: Key, ip: InternPool) std.fmt.Formatter(formatType) { + pub fn fmt(key: Key, ip: InternPool) std.fmt.Formatter(format) { return .{ .data = .{ - .ty = ty, - .ip = ip, - } }; - } - - pub fn fmtValue(value: Key, ty: Index, ip: InternPool) std.fmt.Formatter(formatValue) { - return .{ .data = .{ - .value = value, - .ty = ty, + .key = key, .ip = ip, } }; } @@ -1023,6 +1029,7 @@ pub const Index = enum(u32) { const_slice_u8_type, anyerror_void_error_union_type, generic_poison_type, + unknown_type, /// `undefined` (untyped) undefined_value, @@ -1042,24 +1049,21 @@ pub const Index = enum(u32) { bool_false, /// `.{}` (untyped) empty_aggregate, + /// `0` (usize) + zero_usize, + /// `1` (usize) + one_usize, the_only_possible_value, generic_poison, + // unknown value of unknown type + unknown_unknown, - unknown = std.math.maxInt(u32) - 1, none = std.math.maxInt(u32), _, - pub fn fmtType(ty: Index, ip: InternPool) std.fmt.Formatter(Key.formatType) { + pub fn fmt(index: Index, ip: InternPool) std.fmt.Formatter(Key.format) { return .{ .data = .{ - .ty = ip.indexToKey(ty), - .ip = ip, - } }; - } - - pub fn fmtValue(value_index: Index, type_index: Index, ip: InternPool) std.fmt.Formatter(Key.formatValue) { - return .{ .data = .{ - .value = ip.indexToKey(value_index), - .ty = type_index, + .key = ip.indexToKey(index), .ip = ip, } }; } @@ -1121,12 +1125,7 @@ pub const Tag = enum(u8) { /// data is index to type type_anyframe, - /// An unsigned integer value that can be represented by u32. - /// data is integer value - int_u32, - /// An unsigned integer value that can be represented by i32. - /// data is integer value bitcasted to u32. - int_i32, + // TODO use a more efficient encoding for small integers /// An unsigned integer value that can be represented by u64. /// data is payload to u64 int_u64, @@ -1154,16 +1153,28 @@ pub const Tag = enum(u8) { /// A float value that can be represented by f128. /// data is payload to f128. float_f128, + /// A comptime float value. + /// data is payload to f128. + float_comptime, /// A byte sequence value. /// data is payload to data begin and length. bytes, + /// A optional value that is not null. + /// data is index to OptionalValue. + optional_value, /// A aggregate (struct) value. /// data is index to Aggregate. aggregate, + /// A slice value. + /// data is index to Slice. + slice, /// A union value. /// data is index to UnionValue. union_value, + /// A unknown value. + /// data is index to type which may also be unknown. + unknown_value, }; pub const SimpleType = enum(u32) { @@ -1208,6 +1219,7 @@ pub const SimpleType = enum(u32) { extern_options, type_info, + unknown, generic_poison, }; @@ -1293,22 +1305,26 @@ pub fn init(gpa: Allocator) Allocator.Error!InternPool { .{ .index = .const_slice_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .size = .Slice, .is_const = true } } }, .{ .index = .anyerror_void_error_union_type, .key = .{ .error_union_type = .{ .error_set_type = .anyerror_type, .payload_type = .void_type } } }, .{ .index = .generic_poison_type, .key = .{ .simple_type = .generic_poison } }, + .{ .index = .unknown_type, .key = .{ .simple_type = .unknown } }, .{ .index = .undefined_value, .key = .{ .simple_value = .undefined_value } }, - .{ .index = .zero, .key = .{ .int_u64_value = 0 } }, - .{ .index = .one, .key = .{ .int_u64_value = 1 } }, + .{ .index = .zero, .key = .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 0 } } }, + .{ .index = .one, .key = .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 1 } } }, .{ .index = .void_value, .key = .{ .simple_value = .void_value } }, .{ .index = .unreachable_value, .key = .{ .simple_value = .unreachable_value } }, .{ .index = .null_value, .key = .{ .simple_value = .null_value } }, .{ .index = .bool_true, .key = .{ .simple_value = .bool_true } }, .{ .index = .bool_false, .key = .{ .simple_value = .bool_false } }, - .{ .index = .empty_aggregate, .key = .{ .aggregate = &.{} } }, + .{ .index = .empty_aggregate, .key = .{ .aggregate = .{ .ty = .none, .values = &.{} } } }, + .{ .index = .zero_usize, .key = .{ .int_u64_value = .{ .ty = .usize_type, .int = 0 } } }, + .{ .index = .one_usize, .key = .{ .int_u64_value = .{ .ty = .usize_type, .int = 1 } } }, .{ .index = .the_only_possible_value, .key = .{ .simple_value = .the_only_possible_value } }, .{ .index = .generic_poison, .key = .{ .simple_value = .generic_poison } }, + .{ .index = .unknown_unknown, .key = .{ .unknown_value = .{ .ty = .unknown_type } } }, }; - const extra_count = 4 * @sizeOf(Pointer) + @sizeOf(ErrorUnion) + 4 * @sizeOf(Function); + const extra_count = 4 * @sizeOf(Pointer) + @sizeOf(ErrorUnion) + 4 * @sizeOf(Function) + 4 * @sizeOf(InternPool.U64Value); try ip.map.ensureTotalCapacity(gpa, items.len); try ip.items.ensureTotalCapacity(gpa, items.len); @@ -1352,7 +1368,6 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { pub fn indexToKey(ip: InternPool, index: Index) Key { assert(index != .none); - assert(index != .unknown); const item = ip.items.get(@enumToInt(index)); const data = item.data; return switch (item.tag) { @@ -1381,71 +1396,33 @@ pub fn indexToKey(ip: InternPool, index: Index) Key { .type_enum => .{ .enum_type = @intToEnum(EnumIndex, data) }, .type_union => .{ .union_type = @intToEnum(UnionIndex, data) }, - .int_u32 => .{ .int_u64_value = @intCast(u32, data) }, - .int_i32 => .{ .int_i64_value = @bitCast(i32, data) }, - .int_u64 => .{ .int_u64_value = ip.extraData(u64, data) }, - .int_i64 => .{ .int_i64_value = ip.extraData(i64, data) }, - .int_big_positive => .{ .int_big_value = .{ - .positive = true, - .limbs = ip.extraData([]const std.math.big.Limb, data), - } }, - .int_big_negative => .{ .int_big_value = .{ - .positive = false, - .limbs = ip.extraData([]const std.math.big.Limb, data), + .int_u64 => .{ .int_u64_value = ip.extraData(U64Value, data) }, + .int_i64 => .{ .int_i64_value = ip.extraData(I64Value, data) }, + .int_big_positive, + .int_big_negative, + => .{ .int_big_value = blk: { + const big_int = ip.extraData(BigIntInternal, data); + break :blk .{ + .ty = big_int.ty, + .int = .{ + .positive = item.tag == .int_big_positive, + .limbs = big_int.limbs, + }, + }; } }, .float_f16 => .{ .float_16_value = @bitCast(f16, @intCast(u16, data)) }, .float_f32 => .{ .float_32_value = @bitCast(f32, data) }, .float_f64 => .{ .float_64_value = ip.extraData(f64, data) }, .float_f80 => .{ .float_80_value = ip.extraData(f80, data) }, .float_f128 => .{ .float_128_value = ip.extraData(f128, data) }, + .float_comptime => .{ .float_comptime_value = ip.extraData(f128, data) }, .bytes => .{ .bytes = ip.extraData([]const u8, data) }, + .optional_value => .{ .optional_value = ip.extraData(OptionalValue, data) }, + .slice => .{ .slice = ip.extraData(Slice, data) }, .aggregate => .{ .aggregate = ip.extraData(Aggregate, data) }, .union_value => .{ .union_value = ip.extraData(UnionValue, data) }, - }; -} - -pub fn indexToTag(ip: InternPool, index: Index) std.builtin.TypeId { - const item = ip.items.get(@enumToInt(index)); - const data = item.data; - return switch (item.tag) { - .simple_type => { - const key = Key{ .simple_type = @intToEnum(SimpleType, data) }; - return key.zigTypeTag(); - }, - - .type_int_signed => .Int, - .type_int_unsigned => .Int, - .type_pointer => .Pointer, - .type_array => .Array, - .type_struct => .Struct, - .type_optional => .Optional, - .type_anyframe => .AnyFrame, - .type_error_union => .ErrorUnion, - .type_error_set => .ErrorSet, - .type_enum => .Enum, - .type_function => .Fn, - .type_union => .Union, - .type_tuple => .Struct, - .type_vector => .Vector, - - .simple_value, - .int_u32, - .int_i32, - .int_u64, - .int_i64, - .int_big_positive, - .int_big_negative, - .float_f16, - .float_f32, - .float_f64, - .float_f80, - .float_f128, - => unreachable, - - .bytes => unreachable, - .aggregate => unreachable, - .union_value => unreachable, + .unknown_value => .{ .unknown_value = .{ .ty = @intToEnum(Index, data) } }, }; } @@ -1467,11 +1444,15 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { .enum_type => |enum_index| @enumToInt(enum_index), .union_type => |union_index| @enumToInt(union_index), - .int_u64_value => |int_val| if (tag == .int_u32) @intCast(u32, int_val) else try ip.addExtra(gpa, int_val), - .int_i64_value => |int_val| if (tag == .int_i32) @bitCast(u32, @intCast(u32, int_val)) else try ip.addExtra(gpa, int_val), - .int_big_value => |big_int_val| try ip.addExtra(gpa, big_int_val.limbs), + .int_u64_value => |int_val| try ip.addExtra(gpa, int_val), + .int_i64_value => |int_val| try ip.addExtra(gpa, int_val), + .int_big_value => |big_int_val| try ip.addExtra(gpa, BigIntInternal{ + .ty = big_int_val.ty, + .limbs = big_int_val.int.limbs, + }), .float_16_value => |float_val| @bitCast(u16, float_val), .float_32_value => |float_val| @bitCast(u32, float_val), + .unknown_value => |unknown_val| @enumToInt(unknown_val.ty), inline else => |data| try ip.addExtra(gpa, data), // TODO sad stage1 noises :( }; @@ -1767,8 +1748,8 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t .c_ulonglong, .c_longdouble, => { - const chosen_bits = chosen_key.intInfo(target, ip).bits; - const candidate_bits = candidate_key.intInfo(target, ip).bits; + const chosen_bits = chosen_key.intInfo(target, ip.*).bits; + const candidate_bits = candidate_key.intInfo(target, ip.*).bits; if (chosen_bits < candidate_bits) { chosen = candidate; @@ -1784,7 +1765,7 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t else => {}, }, .int_type => |chosen_info| { - if (chosen_info.bits < candidate_key.intInfo(target, ip).bits) { + if (chosen_info.bits < candidate_key.intInfo(target, ip.*).bits) { chosen = candidate; chosen_i = candidate_i; } @@ -1856,8 +1837,8 @@ pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, t .c_ulonglong, .c_longdouble, => { - const chosen_bits = chosen_key.intInfo(target, ip).bits; - const candidate_bits = candidate_key.intInfo(target, ip).bits; + const chosen_bits = chosen_key.intInfo(target, ip.*).bits; + const candidate_bits = candidate_key.intInfo(target, ip.*).bits; if (chosen_bits < candidate_bits) { chosen = candidate; @@ -2310,8 +2291,8 @@ fn coerceInMemoryAllowed( switch (dest_tag) { .Int => { - const dest_info = dest_key.intInfo(target, ip); - const src_info = src_key.intInfo(target, ip); + const dest_info = dest_key.intInfo(target, ip.*); + const src_info = src_key.intInfo(target, ip.*); if (dest_info.signedness == src_info.signedness and dest_info.bits == src_info.bits) return .ok; @@ -2730,14 +2711,6 @@ inline fn panicOrElse(message: []const u8, value: anytype) @TypeOf(value) { // TESTS // --------------------------------------------- -fn testExpectFmtType(ip: InternPool, index: Index, expected: []const u8) !void { - try std.testing.expectFmt(expected, "{}", .{index.fmtType(ip)}); -} - -fn testExpectFmtValue(ip: InternPool, val: Index, ty: Index, expected: []const u8) !void { - try std.testing.expectFmt(expected, "{}", .{val.fmtValue(ty, ip)}); -} - test "simple types" { const gpa = std.testing.allocator; @@ -2755,16 +2728,16 @@ test "simple types" { const bool_true = try ip.get(gpa, .{ .simple_value = .bool_true }); const bool_false = try ip.get(gpa, .{ .simple_value = .bool_false }); - try testExpectFmtType(ip, null_type, "@TypeOf(null)"); - try testExpectFmtType(ip, undefined_type, "@TypeOf(undefined)"); - try testExpectFmtType(ip, enum_literal_type, "@TypeOf(.enum_literal)"); + try expectFmt("@TypeOf(null)", "{}", .{null_type.fmt(ip)}); + try expectFmt("@TypeOf(undefined)", "{}", .{undefined_type.fmt(ip)}); + try expectFmt("@TypeOf(.enum_literal)", "{}", .{enum_literal_type.fmt(ip)}); - try testExpectFmtValue(ip, undefined_value, .none, "undefined"); - try testExpectFmtValue(ip, void_value, .none, "{}"); - try testExpectFmtValue(ip, unreachable_value, .none, "unreachable"); - try testExpectFmtValue(ip, null_value, .none, "null"); - try testExpectFmtValue(ip, bool_true, .none, "true"); - try testExpectFmtValue(ip, bool_false, .none, "false"); + try expectFmt("undefined", "{}", .{undefined_value.fmt(ip)}); + try expectFmt("{}", "{}", .{void_value.fmt(ip)}); + try expectFmt("unreachable", "{}", .{unreachable_value.fmt(ip)}); + try expectFmt("null", "{}", .{null_value.fmt(ip)}); + try expectFmt("true", "{}", .{bool_true.fmt(ip)}); + try expectFmt("false", "{}", .{bool_false.fmt(ip)}); } test "int type" { @@ -2784,9 +2757,9 @@ test "int type" { try expect(i16_type != another_i32_type); try expect(i16_type != u7_type); - try testExpectFmtType(ip, i32_type, "i32"); - try testExpectFmtType(ip, i16_type, "i16"); - try testExpectFmtType(ip, u7_type, "u7"); + try expectFmt("i32", "{}", .{i32_type.fmt(ip)}); + try expectFmt("i16", "{}", .{i16_type.fmt(ip)}); + try expectFmt("u7", "{}", .{u7_type.fmt(ip)}); } test "int value" { @@ -2795,21 +2768,14 @@ test "int value" { var ip = try InternPool.init(gpa); defer ip.deinit(gpa); - const unsigned_zero_value = try ip.get(gpa, .{ .int_u64_value = 0 }); - const unsigned_one_value = try ip.get(gpa, .{ .int_u64_value = 1 }); - const signed_zero_value = try ip.get(gpa, .{ .int_i64_value = 0 }); - const signed_one_value = try ip.get(gpa, .{ .int_i64_value = 1 }); + const unsigned_zero_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .u64_type, .int = 0 } }); + const unsigned_one_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .u64_type, .int = 1 } }); + const signed_zero_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .i64_type, .int = 0 } }); + const signed_one_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .i64_type, .int = 1 } }); - const u64_max_value = try ip.get(gpa, .{ .int_u64_value = std.math.maxInt(u64) }); - const i64_max_value = try ip.get(gpa, .{ .int_i64_value = std.math.maxInt(i64) }); - const i64_min_value = try ip.get(gpa, .{ .int_i64_value = std.math.minInt(i64) }); - - const tags = ip.items.items(.tag); - try expect(tags[@enumToInt(unsigned_one_value)] == .int_u32); - try expect(tags[@enumToInt(signed_one_value)] == .int_i32); - try expect(tags[@enumToInt(u64_max_value)] == .int_u64); - try expect(tags[@enumToInt(i64_max_value)] == .int_i64); - try expect(tags[@enumToInt(i64_min_value)] == .int_i64); + const u64_max_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .u64_type, .int = std.math.maxInt(u64) } }); + const i64_max_value = try ip.get(gpa, .{ .int_i64_value = .{ .ty = .i64_type, .int = std.math.maxInt(i64) } }); + const i64_min_value = try ip.get(gpa, .{ .int_i64_value = .{ .ty = .i64_type, .int = std.math.minInt(i64) } }); try expect(unsigned_zero_value != unsigned_one_value); try expect(unsigned_one_value != signed_zero_value); @@ -2819,14 +2785,14 @@ test "int value" { try expect(u64_max_value != i64_max_value); try expect(i64_max_value != i64_min_value); - try testExpectFmtValue(ip, unsigned_zero_value, undefined, "0"); - try testExpectFmtValue(ip, unsigned_one_value, undefined, "1"); - try testExpectFmtValue(ip, signed_zero_value, undefined, "0"); - try testExpectFmtValue(ip, signed_one_value, undefined, "1"); + try expectFmt("0", "{}", .{unsigned_zero_value.fmt(ip)}); + try expectFmt("1", "{}", .{unsigned_one_value.fmt(ip)}); + try expectFmt("0", "{}", .{signed_zero_value.fmt(ip)}); + try expectFmt("1", "{}", .{signed_one_value.fmt(ip)}); - try testExpectFmtValue(ip, u64_max_value, undefined, "18446744073709551615"); - try testExpectFmtValue(ip, i64_max_value, undefined, "9223372036854775807"); - try testExpectFmtValue(ip, i64_min_value, undefined, "-9223372036854775808"); + try expectFmt("18446744073709551615", "{}", .{u64_max_value.fmt(ip)}); + try expectFmt("9223372036854775807", "{}", .{i64_max_value.fmt(ip)}); + try expectFmt("-9223372036854775808", "{}", .{i64_min_value.fmt(ip)}); } test "big int value" { @@ -2842,11 +2808,17 @@ test "big int value" { try result.pow(&a, 128); - const positive_big_int_value = try ip.get(gpa, .{ .int_big_value = result.toConst() }); - const negative_big_int_value = try ip.get(gpa, .{ .int_big_value = result.toConst().negate() }); + const positive_big_int_value = try ip.get(gpa, .{ .int_big_value = .{ + .ty = .comptime_int_type, + .int = result.toConst(), + } }); + const negative_big_int_value = try ip.get(gpa, .{ .int_big_value = .{ + .ty = .comptime_int_type, + .int = result.toConst().negate(), + } }); - try testExpectFmtValue(ip, positive_big_int_value, .none, "340282366920938463463374607431768211456"); - try testExpectFmtValue(ip, negative_big_int_value, .none, "-340282366920938463463374607431768211456"); + try expectFmt("340282366920938463463374607431768211456", "{}", .{positive_big_int_value.fmt(ip)}); + try expectFmt("-340282366920938463463374607431768211456", "{}", .{negative_big_int_value.fmt(ip)}); } test "float type" { @@ -2872,11 +2844,11 @@ test "float type" { try expect(f32_type == another_f32_type); try expect(f64_type == another_f64_type); - try testExpectFmtType(ip, f16_type, "f16"); - try testExpectFmtType(ip, f32_type, "f32"); - try testExpectFmtType(ip, f64_type, "f64"); - try testExpectFmtType(ip, f80_type, "f80"); - try testExpectFmtType(ip, f128_type, "f128"); + try expectFmt("f16", "{}", .{f16_type.fmt(ip)}); + try expectFmt("f32", "{}", .{f32_type.fmt(ip)}); + try expectFmt("f64", "{}", .{f64_type.fmt(ip)}); + try expectFmt("f80", "{}", .{f80_type.fmt(ip)}); + try expectFmt("f128", "{}", .{f128_type.fmt(ip)}); } test "float value" { @@ -2921,20 +2893,20 @@ test "float value" { try expect(ip.indexToKey(f32_zero_value).eql(ip.indexToKey(f32_zero_value))); try expect(!ip.indexToKey(f32_zero_value).eql(ip.indexToKey(f32_nzero_value))); - try testExpectFmtValue(ip, f16_value, undefined, "0.25"); - try testExpectFmtValue(ip, f32_value, undefined, "0.5"); - try testExpectFmtValue(ip, f64_value, undefined, "1"); - try testExpectFmtValue(ip, f80_value, undefined, "2"); - try testExpectFmtValue(ip, f128_value, undefined, "2.75"); + try expectFmt("0.25", "{}", .{f16_value.fmt(ip)}); + try expectFmt("0.5", "{}", .{f32_value.fmt(ip)}); + try expectFmt("1", "{}", .{f64_value.fmt(ip)}); + try expectFmt("2", "{}", .{f80_value.fmt(ip)}); + try expectFmt("2.75", "{}", .{f128_value.fmt(ip)}); - try testExpectFmtValue(ip, f32_nan_value, undefined, "nan"); - try testExpectFmtValue(ip, f32_qnan_value, undefined, "nan"); + try expectFmt("nan", "{}", .{f32_nan_value.fmt(ip)}); + try expectFmt("nan", "{}", .{f32_qnan_value.fmt(ip)}); - try testExpectFmtValue(ip, f32_inf_value, undefined, "inf"); - try testExpectFmtValue(ip, f32_ninf_value, undefined, "-inf"); + try expectFmt("inf", "{}", .{f32_inf_value.fmt(ip)}); + try expectFmt("-inf", "{}", .{f32_ninf_value.fmt(ip)}); - try testExpectFmtValue(ip, f32_zero_value, undefined, "0"); - try testExpectFmtValue(ip, f32_nzero_value, undefined, "-0"); + try expectFmt("0", "{}", .{f32_zero_value.fmt(ip)}); + try expectFmt("-0", "{}", .{f32_nzero_value.fmt(ip)}); } test "pointer type" { @@ -3004,17 +2976,17 @@ test "pointer type" { try expect(@"[*:0]u32" != @"[:0]u32"); try expect(@"[:0]u32" != @"[*c]u32"); - try testExpectFmtType(ip, @"*i32", "*i32"); - try testExpectFmtType(ip, @"*u32", "*u32"); - try testExpectFmtType(ip, @"*const volatile u32", "*const volatile u32"); - try testExpectFmtType(ip, @"*align(4:2:3) u32", "*align(4:2:3) u32"); - try testExpectFmtType(ip, @"*addrspace(.shared) const u32", "*addrspace(.shared) const u32"); + try expectFmt("*i32", "{}", .{@"*i32".fmt(ip)}); + try expectFmt("*u32", "{}", .{@"*u32".fmt(ip)}); + try expectFmt("*const volatile u32", "{}", .{@"*const volatile u32".fmt(ip)}); + try expectFmt("*align(4:2:3) u32", "{}", .{@"*align(4:2:3) u32".fmt(ip)}); + try expectFmt("*addrspace(.shared) const u32", "{}", .{@"*addrspace(.shared) const u32".fmt(ip)}); - try testExpectFmtType(ip, @"[*]u32", "[*]u32"); - try testExpectFmtType(ip, @"[*:0]u32", "[*:0]u32"); - try testExpectFmtType(ip, @"[]u32", "[]u32"); - try testExpectFmtType(ip, @"[:0]u32", "[:0]u32"); - try testExpectFmtType(ip, @"[*c]u32", "[*c]u32"); + try expectFmt("[*]u32", "{}", .{@"[*]u32".fmt(ip)}); + try expectFmt("[*:0]u32", "{}", .{@"[*:0]u32".fmt(ip)}); + try expectFmt("[]u32", "{}", .{@"[]u32".fmt(ip)}); + try expectFmt("[:0]u32", "{}", .{@"[:0]u32".fmt(ip)}); + try expectFmt("[*c]u32", "{}", .{@"[*c]u32".fmt(ip)}); } test "optional type" { @@ -3023,18 +2995,27 @@ test "optional type" { var ip = try InternPool.init(gpa); defer ip.deinit(gpa); - const u64_42_value = try ip.get(gpa, .{ .int_u64_value = 42 }); - const i32_optional_type = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .i32_type } }); const u32_optional_type = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .u32_type } }); try expect(i32_optional_type != u32_optional_type); - try testExpectFmtType(ip, i32_optional_type, "?i32"); - try testExpectFmtType(ip, u32_optional_type, "?u32"); + try expectFmt("?i32", "{}", .{i32_optional_type.fmt(ip)}); + try expectFmt("?u32", "{}", .{u32_optional_type.fmt(ip)}); +} - try testExpectFmtValue(ip, .null_value, u32_optional_type, "null"); - try testExpectFmtValue(ip, u64_42_value, u32_optional_type, "42"); +test "optional value" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const u32_optional_type = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .u32_type } }); + + const u64_42_value = try ip.get(gpa, .{ .int_u64_value = .{ .ty = .u64_type, .int = 42 } }); + const optional_42_value = try ip.get(gpa, .{ .optional_value = .{ .ty = u32_optional_type, .val = u64_42_value } }); + + try expectFmt("42", "{}", .{optional_42_value.fmt(ip)}); } test "error set type" { @@ -3060,9 +3041,9 @@ test "error set type" { try expect(empty_error_set != foo_bar_baz_set); try expect(foo_bar_baz_set != foo_bar_set); - try testExpectFmtType(ip, empty_error_set, "error{}"); - try testExpectFmtType(ip, foo_bar_baz_set, "error{foo,bar,baz}"); - try testExpectFmtType(ip, foo_bar_set, "error{foo,bar}"); + try expectFmt("error{}", "{}", .{empty_error_set.fmt(ip)}); + try expectFmt("error{foo,bar,baz}", "{}", .{foo_bar_baz_set.fmt(ip)}); + try expectFmt("error{foo,bar}", "{}", .{foo_bar_set.fmt(ip)}); } test "error union type" { @@ -3079,7 +3060,7 @@ test "error union type" { .payload_type = bool_type, } }); - try testExpectFmtType(ip, @"error{}!bool", "error{}!bool"); + try expectFmt("error{}!bool", "{}", .{@"error{}!bool".fmt(ip)}); } test "array type" { @@ -3100,8 +3081,8 @@ test "array type" { try expect(i32_3_array_type != u32_0_0_array_type); - try testExpectFmtType(ip, i32_3_array_type, "[3]i32"); - try testExpectFmtType(ip, u32_0_0_array_type, "[3:0]u32"); + try expectFmt("[3]i32", "{}", .{i32_3_array_type.fmt(ip)}); + try expectFmt("[3:0]u32", "{}", .{u32_0_0_array_type.fmt(ip)}); } test "struct value" { @@ -3119,15 +3100,15 @@ test "struct value" { }); const struct_type = try ip.get(gpa, .{ .struct_type = struct_index }); const struct_info = ip.getStruct(struct_index); - try struct_info.fields.put(gpa, "foo", .{ .ty = .i32_type }); + try struct_info.fields.put(gpa, "foo", .{ .ty = .usize_type }); try struct_info.fields.put(gpa, "bar", .{ .ty = .bool_type }); - const one_value = try ip.get(gpa, .{ .int_i64_value = 1 }); - const true_value = try ip.get(gpa, .{ .simple_value = .bool_true }); + const aggregate_value = try ip.get(gpa, .{ .aggregate = .{ + .ty = struct_type, + .values = &.{ .one_usize, .bool_true }, + } }); - const aggregate_value = try ip.get(gpa, .{ .aggregate = &.{ one_value, true_value } }); - - try ip.testExpectFmtValue(aggregate_value, struct_type, ".{.foo = 1, .bar = true}"); + try expectFmt(".{.foo = 1, .bar = true}", "{}", .{aggregate_value.fmt(ip)}); } test "function type" { @@ -3166,10 +3147,10 @@ test "function type" { .calling_convention = .C, } }); - try testExpectFmtType(ip, @"fn(i32) bool", "fn(i32) bool"); - try testExpectFmtType(ip, @"fn(comptime type, noalias i32) type", "fn(comptime type, noalias i32) type"); - try testExpectFmtType(ip, @"fn(i32, ...) type", "fn(i32, ...) type"); - try testExpectFmtType(ip, @"fn() align(4) callconv(.C) type", "fn() align(4) callconv(.C) type"); + try expectFmt("fn(i32) bool", "{}", .{@"fn(i32) bool".fmt(ip)}); + try expectFmt("fn(comptime type, noalias i32) type", "{}", .{@"fn(comptime type, noalias i32) type".fmt(ip)}); + try expectFmt("fn(i32, ...) type", "{}", .{@"fn(i32, ...) type".fmt(ip)}); + try expectFmt("fn() align(4) callconv(.C) type", "{}", .{@"fn() align(4) callconv(.C) type".fmt(ip)}); } test "union value" { @@ -3178,7 +3159,6 @@ test "union value" { var ip = try InternPool.init(gpa); defer ip.deinit(gpa); - const int_value = try ip.get(gpa, .{ .int_u64_value = 1 }); const f16_value = try ip.get(gpa, .{ .float_16_value = 0.25 }); const union_index = try ip.createUnion(gpa, .{ @@ -3190,20 +3170,22 @@ test "union value" { }); const union_type = try ip.get(gpa, .{ .union_type = union_index }); const union_info = ip.getUnion(union_index); - try union_info.fields.put(gpa, "int", .{ .ty = .u32_type, .alignment = 0 }); + try union_info.fields.put(gpa, "int", .{ .ty = .usize_type, .alignment = 0 }); try union_info.fields.put(gpa, "float", .{ .ty = .f16_type, .alignment = 0 }); const union_value1 = try ip.get(gpa, .{ .union_value = .{ + .ty = union_type, .field_index = 0, - .val = int_value, + .val = .one_usize, } }); const union_value2 = try ip.get(gpa, .{ .union_value = .{ + .ty = union_type, .field_index = 1, .val = f16_value, } }); - try testExpectFmtValue(ip, union_value1, union_type, ".{ .int = 1 }"); - try testExpectFmtValue(ip, union_value2, union_type, ".{ .float = 0.25 }"); + try expectFmt(".{ .int = 1 }", "{}", .{union_value1.fmt(ip)}); + try expectFmt(".{ .float = 0.25 }", "{}", .{union_value2.fmt(ip)}); } test "anyframe type" { @@ -3217,8 +3199,8 @@ test "anyframe type" { try expect(@"anyframe->i32" != @"anyframe->bool"); - try testExpectFmtType(ip, @"anyframe->i32", "anyframe->i32"); - try testExpectFmtType(ip, @"anyframe->bool", "anyframe->bool"); + try expectFmt("anyframe->i32", "{}", .{@"anyframe->i32".fmt(ip)}); + try expectFmt("anyframe->bool", "{}", .{@"anyframe->bool".fmt(ip)}); } test "vector type" { @@ -3238,8 +3220,8 @@ test "vector type" { try expect(@"@Vector(2,u32)" != @"@Vector(2,bool)"); - try testExpectFmtType(ip, @"@Vector(2,u32)", "@Vector(2,u32)"); - try testExpectFmtType(ip, @"@Vector(2,bool)", "@Vector(2,bool)"); + try expectFmt("@Vector(2,u32)", "{}", .{@"@Vector(2,u32)".fmt(ip)}); + try expectFmt("@Vector(2,bool)", "{}", .{@"@Vector(2,bool)".fmt(ip)}); } test "bytes value" { @@ -3582,16 +3564,16 @@ fn testResolvePeerTypes(ip: *InternPool, a: Index, b: Index, expected: Index) !v fn testResolvePeerTypesInOrder(ip: *InternPool, lhs: Index, rhs: Index, expected: Index) !void { const actual = try resolvePeerTypes(ip, std.testing.allocator, &.{ lhs, rhs }, builtin.target); - try expectEqualTypes(ip, expected, actual); + try expectEqualTypes(ip.*, expected, actual); } -fn expectEqualTypes(ip: *InternPool, expected: Index, actual: Index) !void { +fn expectEqualTypes(ip: InternPool, expected: Index, actual: Index) !void { if (expected == actual) return; const allocator = std.testing.allocator; - const expected_type = if (expected == .none) @tagName(Index.none) else try std.fmt.allocPrint(allocator, "{}", .{expected.fmtType(ip.*)}); + const expected_type = if (expected == .none) @tagName(Index.none) else try std.fmt.allocPrint(allocator, "{}", .{expected.fmt(ip)}); defer if (expected != .none) allocator.free(expected_type); - const actual_type = if (actual == .none) @tagName(Index.none) else try std.fmt.allocPrint(allocator, "{}", .{actual.fmtType(ip.*)}); + const actual_type = if (actual == .none) @tagName(Index.none) else try std.fmt.allocPrint(allocator, "{}", .{actual.fmt(ip)}); defer if (actual != .none) allocator.free(actual_type); std.debug.print("expected `{s}`, found `{s}`\n", .{ expected_type, actual_type }); diff --git a/src/analyser/completions.zig b/src/analyser/completions.zig index 4c2027e..8290f43 100644 --- a/src/analyser/completions.zig +++ b/src/analyser/completions.zig @@ -4,31 +4,36 @@ const types = @import("../lsp.zig"); const Ast = std.zig.Ast; +/// generates a list of dot completions for the given typed-value in `index` +/// the given `index` must belong to the given InternPool pub fn dotCompletions( arena: std.mem.Allocator, completions: *std.ArrayListUnmanaged(types.CompletionItem), ip: *InternPool, - ty: InternPool.Index, - val: InternPool.Index, + index: InternPool.Index, + is_type_val: bool, node: ?Ast.Node.Index, ) error{OutOfMemory}!void { + std.debug.assert(index != .none); _ = node; - const key = ip.indexToKey(ty); - const inner_key = switch (key) { - .pointer_type => |info| if (info.size == .One) ip.indexToKey(info.elem_type) else key, - else => key, + const index_key = ip.indexToKey(index); + const val: InternPool.Key = if (is_type_val) index_key else .{ .unknown_value = .{ .ty = index } }; + const ty: InternPool.Key = if (is_type_val) ip.indexToKey(index_key.typeOf()) else index_key; + + const inner_ty = switch (ty) { + .pointer_type => |info| if (info.size == .One) ip.indexToKey(info.elem_type) else ty, + else => ty, }; - switch (inner_key) { + switch (inner_ty) { .simple_type => |simple| switch (simple) { .type => { - const ty_key = ip.indexToKey(val); - const namespace = ty_key.getNamespace(ip.*); + const namespace = val.getNamespace(ip.*); if (namespace != .none) { // TODO lookup in namespace } - switch (ty_key) { + switch (val) { .error_set_type => |error_set_info| { for (error_set_info.names) |name| { const error_name = ip.indexToKey(name).bytes; @@ -64,7 +69,7 @@ pub fn dotCompletions( try completions.append(arena, .{ .label = "ptr", .kind = .Field, - .detail = try std.fmt.allocPrint(arena, "ptr: {}", .{many_ptr_info.fmtType(ip.*)}), + .detail = try std.fmt.allocPrint(arena, "ptr: {}", .{many_ptr_info.fmt(ip.*)}), }); try completions.append(arena, .{ .label = "len", @@ -80,7 +85,7 @@ pub fn dotCompletions( } }, .array_type => |array_info| { - try completions.append(arena, types.CompletionItem{ + try completions.append(arena, .{ .label = "len", .kind = .Field, .detail = try std.fmt.allocPrint(arena, "const len: usize ({d})", .{array_info.len}), // TODO how should this be displayed @@ -93,7 +98,7 @@ pub fn dotCompletions( while (field_it.next()) |entry| { const label = entry.key_ptr.*; const field = entry.value_ptr.*; - completions.appendAssumeCapacity(types.CompletionItem{ + completions.appendAssumeCapacity(.{ .label = label, .kind = .Field, .detail = try std.fmt.allocPrint(arena, "{s}: {}", .{ @@ -107,7 +112,7 @@ pub fn dotCompletions( try completions.append(arena, .{ .label = "?", .kind = .Operator, - .detail = try std.fmt.allocPrint(arena, "{}", .{optional_info.payload_type.fmtType(ip.*)}), + .detail = try std.fmt.allocPrint(arena, "{}", .{optional_info.payload_type.fmt(ip.*)}), }); }, .enum_type => |enum_index| { @@ -116,7 +121,7 @@ pub fn dotCompletions( try completions.append(arena, .{ .label = field_name, .kind = .Field, - .detail = try std.fmt.allocPrint(arena, "{}", .{field_value.fmtValue(enum_info.tag_type, ip.*)}), + .detail = try std.fmt.allocPrint(arena, "{}", .{field_value.fmt(ip.*)}), }); } }, @@ -130,9 +135,9 @@ pub fn dotCompletions( .label = label, .kind = .Field, .detail = if (field.alignment != 0) - try std.fmt.allocPrint(arena, "{s}: align({d}) {}", .{ label, field.alignment, field.ty.fmtType(ip.*) }) + try std.fmt.allocPrint(arena, "{s}: align({d}) {}", .{ label, field.alignment, field.ty.fmt(ip.*) }) else - try std.fmt.allocPrint(arena, "{s}: {}", .{ label, field.ty.fmtType(ip.*) }), + try std.fmt.allocPrint(arena, "{s}: {}", .{ label, field.ty.fmt(ip.*) }), }); } }, @@ -141,7 +146,7 @@ pub fn dotCompletions( try completions.append(arena, .{ .label = try std.fmt.allocPrint(arena, "{d}", .{i}), .kind = .Field, - .detail = try std.fmt.allocPrint(arena, "{d}: {}", .{ i, tuple_ty.fmtType(ip.*) }), + .detail = try std.fmt.allocPrint(arena, "{d}: {}", .{ i, tuple_ty.fmt(ip.*) }), }); } }, @@ -162,11 +167,15 @@ pub fn dotCompletions( .float_64_value, .float_80_value, .float_128_value, + .float_comptime_value, => unreachable, .bytes, + .optional_value, + .slice, .aggregate, .union_value, + .unknown_value, => unreachable, } } @@ -194,9 +203,9 @@ fn formatFieldDetail( if (field.alignment != 0) { try writer.print("align({d}) ", .{field.alignment}); } - try writer.print("{}", .{field.ty.fmtType(ctx.ip.*)}); + try writer.print("{}", .{field.ty.fmt(ctx.ip.*)}); if (field.default_value != .none) { - try writer.print(" = {},", .{field.default_value.fmtValue(field.ty, ctx.ip.*)}); + try writer.print(" = {},", .{field.default_value.fmt(ctx.ip.*)}); } } diff --git a/src/analysis.zig b/src/analysis.zig index b348029..9cdd8cb 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -788,6 +788,7 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, node_handle: NodeWithHan } return null; }; + const is_type_val = interpreter.ip.indexToKey(value.index).typeOf() == .type_type; return TypeWithHandle{ .type = .{ @@ -795,7 +796,7 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, node_handle: NodeWithHan .interpreter = interpreter, .value = value, } }, - .is_type_val = value.ty == InternPool.Index.type_type, + .is_type_val = is_type_val, }, .handle = node_handle.handle, }; diff --git a/tests/language_features/comptime_interpreter.zig b/tests/language_features/comptime_interpreter.zig index 378bf17..b5eedb7 100644 --- a/tests/language_features/comptime_interpreter.zig +++ b/tests/language_features/comptime_interpreter.zig @@ -14,39 +14,50 @@ const offsets = zls.offsets; const allocator: std.mem.Allocator = std.testing.allocator; test "ComptimeInterpreter - primitive types" { - try testExpr("true", .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); - try testExpr("false", .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); - try testExpr("5", .{ .simple_type = .comptime_int }, .{ .int_u64_value = 5 }); - // TODO try testExpr("-2", .{ .simple_type = .comptime_int }, .{ .int_i64_value = -2 }); - try testExpr("3.0", .{ .simple_type = .comptime_float }, null); + try testExpr("true", .{ .simple_value = .bool_true }); + try testExpr("false", .{ .simple_value = .bool_false }); + try testExpr("5", .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 5 } }); + // TODO try testExpr("-2", .{ .int_i64_value = .{ .ty = .comptime_int, .int = -2 } }); + try testExpr("3.0", .{ .float_comptime_value = 3.0 }); - try testExpr("null", .{ .simple_type = .null_type }, .{ .simple_value = .null_value }); - try testExpr("void", .{ .simple_type = .type }, .{ .simple_type = .void }); - try testExpr("undefined", .{ .simple_type = .undefined_type }, .{ .simple_value = .undefined_value }); - try testExpr("noreturn", .{ .simple_type = .type }, .{ .simple_type = .noreturn }); + try testExpr("null", .{ .simple_value = .null_value }); + try testExpr("void", .{ .simple_type = .void }); + try testExpr("undefined", .{ .simple_value = .undefined_value }); + try testExpr("noreturn", .{ .simple_type = .noreturn }); } test "ComptimeInterpreter - expressions" { if (true) return error.SkipZigTest; // TODO - try testExpr("5 + 3", .{ .simple_type = .comptime_int }, .{ .int_u64_value = 8 }); - try testExpr("5.2 + 4.2", .{ .simple_type = .comptime_float }, null); + try testExpr("5 + 3", .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 8 } }); + // try testExpr("5.2 + 4.2", .{ .simple_type = .comptime_float }, null); - try testExpr("3 == 3", .{ .simple_type = .bool }, .{ .simple_valueclear = .bool_true }); - try testExpr("5.2 == 2.1", .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); + try testExpr("3 == 3", .{ .simple_valueclear = .bool_true }); + try testExpr("5.2 == 2.1", .{ .simple_value = .bool_false }); - try testExpr("@as(?bool, null) orelse true", .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); + try testExpr("@as(?bool, null) orelse true", .{ .simple_value = .bool_true }); } test "ComptimeInterpreter - builtins" { if (true) return error.SkipZigTest; // TODO - try testExpr("@as(bool, true)", .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); - try testExpr("@as(u32, 3)", .{ .int_type = .{ - .signedness = .unsigned, - .bits = 32, - } }, .{ .int_u64_value = 3 }); + try testExpr("@as(bool, true)", .{ .simple_value = .bool_true }); + try testExpr("@as(u32, 3)", .{ .int_u64_value = .{ .ty = .u32_type, .int = 3 } }); +} + +test "ComptimeInterpreter - @TypeOf" { + try testExpr("@TypeOf(bool)", .{ .simple_type = .type }); + try testExpr("@TypeOf(5)", .{ .simple_type = .comptime_int }); + try testExpr("@TypeOf(3.14)", .{ .simple_type = .comptime_float }); + + try testExpr("@TypeOf(bool, u32)", .{ .simple_type = .type }); + try testExpr("@TypeOf(true, false)", .{ .simple_type = .bool }); + try testExpr("@TypeOf(3, 2)", .{ .simple_type = .comptime_int }); + try testExpr("@TypeOf(3.14, 2)", .{ .simple_type = .comptime_float }); + + try testExpr("@TypeOf(null, 2)", .{ .optional_type = .{ .payload_type = .comptime_int_type } }); } test "ComptimeInterpreter - string literal" { + if (true) return error.SkipZigTest; // TODO var context = try Context.init( \\const foobarbaz = "hello world!"; \\ @@ -64,12 +75,12 @@ test "ComptimeInterpreter - labeled block" { \\blk: { \\ break :blk true; \\} - , .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); + , .{ .simple_value = .bool_true }); try testExpr( \\blk: { \\ break :blk 3; \\} - , .{ .simple_type = .comptime_int }, .{ .int_u64_value = 3 }); + , .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 3 } }); } test "ComptimeInterpreter - if" { @@ -77,18 +88,18 @@ test "ComptimeInterpreter - if" { \\blk: { \\ break :blk if (true) true else false; \\} - , .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); + , .{ .simple_value = .bool_true }); try testExpr( \\blk: { \\ break :blk if (false) true else false; \\} - , .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); + , .{ .simple_value = .bool_false }); try testExpr( \\blk: { \\ if (false) break :blk true; \\ break :blk false; \\} - , .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); + , .{ .simple_value = .bool_false }); // TODO // try testExpr( // \\outer: { @@ -97,7 +108,7 @@ test "ComptimeInterpreter - if" { // \\ }) break :outer true; // \\ break :outer false; // \\} - // , .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); + // , .{ .simple_value = .bool_true }); } test "ComptimeInterpreter - variable lookup" { @@ -106,7 +117,7 @@ test "ComptimeInterpreter - variable lookup" { \\ var foo = 42; \\ break :blk foo; \\} - , .{ .simple_type = .comptime_int }, .{ .int_u64_value = 42 }); + , .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 42 } }); try testExpr( \\blk: { \\ var foo = 1; @@ -114,7 +125,7 @@ test "ComptimeInterpreter - variable lookup" { \\ var baz = 3; \\ break :blk bar; \\} - , .{ .simple_type = .comptime_int }, .{ .int_u64_value = 2 }); + , .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 2 } }); var context = try Context.init( \\const bar = foo; @@ -123,26 +134,25 @@ test "ComptimeInterpreter - variable lookup" { defer context.deinit(); const result = try context.interpret(context.findVar("bar")); - try expectEqualKey(context.interpreter.ip, .{ .simple_type = .comptime_int }, result.ty); - try expectEqualKey(context.interpreter.ip, .{ .int_u64_value = 3 }, result.val); + try expectEqualKey(context.interpreter.ip, .{ .int_u64_value = .{ .ty = .comptime_int_type, .int = 3 } }, result.val); } test "ComptimeInterpreter - field access" { try testExpr( \\blk: { \\ const foo: struct {alpha: u64, beta: bool} = undefined; - \\ break :blk foo.beta; + \\ break :blk @TypeOf(foo.beta); \\} - , .{ .simple_type = .bool }, null); + , .{ .simple_type = .bool }); try testExpr( \\blk: { \\ const foo: struct {alpha: u64, beta: bool} = undefined; - \\ break :blk foo.alpha; + \\ break :blk @TypeOf(foo.alpha); \\} , .{ .int_type = .{ .signedness = .unsigned, .bits = 64, - } }, null); + } }); } test "ComptimeInterpreter - optional operations" { @@ -152,13 +162,13 @@ test "ComptimeInterpreter - optional operations" { \\ const foo: ?bool = true; \\ break :blk foo.?; \\} - , .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); + , .{ .simple_value = .bool_true }); try testExpr( \\blk: { \\ const foo: ?bool = true; \\ break :blk foo == null; \\} - , .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); + , .{ .simple_value = .bool_false }); } test "ComptimeInterpreter - pointer operations" { @@ -168,20 +178,20 @@ test "ComptimeInterpreter - pointer operations" { \\ const foo: []const u8 = ""; \\ break :blk foo.len; \\} - , .{ .simple_type = .usize }, .{ .bytes = "" }); + , .{ .int_u64_value = .{ .ty = .usize_type, .int = 0 } }); try testExpr( \\blk: { \\ const foo = true; \\ break :blk &foo; \\} - , @panic("TODO"), .{ .simple_value = .bool_true }); + , .{ .simple_value = .bool_true }); try testExpr( \\blk: { \\ const foo = true; \\ const bar = &foo; \\ break :blk bar.*; \\} - , @panic("TODO"), .{ .simple_value = .bool_true }); + , .{ .simple_value = .bool_true }); } test "ComptimeInterpreter - call return primitive type" { @@ -357,20 +367,24 @@ const Context = struct { args[i] = .{ .interpreter = self.interpreter, .node_idx = 0, - .ty = try self.interpreter.ip.get(self.interpreter.allocator, argument.ty), - .val = if (argument.val) |val| try self.interpreter.ip.get(self.interpreter.allocator, val) else .none, + .index = if (argument.val) |val| + try self.interpreter.ip.get(self.interpreter.allocator, val) + else + try self.interpreter.ip.get(self.interpreter.allocator, .{ + .unknown_value = .{ .ty = try self.interpreter.ip.get(self.interpreter.allocator, argument.ty) }, + }), }; } const namespace = @intToEnum(ComptimeInterpreter.Namespace.Index, 0); // root namespace const result = (try self.interpreter.call(namespace, func_node, args, .{})).result; - try std.testing.expect(result == .value); - try std.testing.expect(result.value.ty != .none); + const val = self.interpreter.ip.indexToKey(result.value.index); + const ty = self.interpreter.ip.indexToKey(val.typeOf()); return KV{ - .ty = self.interpreter.ip.indexToKey(result.value.ty), - .val = if (result.value.val == .none) null else self.interpreter.ip.indexToKey(result.value.val), + .ty = ty, + .val = val, }; } @@ -378,11 +392,12 @@ const Context = struct { const namespace = @intToEnum(ComptimeInterpreter.Namespace.Index, 0); // root namespace const result = try (try self.interpreter.interpret(node, namespace, .{})).getValue(); - try std.testing.expect(result.ty != .none); + const val = self.interpreter.ip.indexToKey(result.index); + const ty = self.interpreter.ip.indexToKey(val.typeOf()); return KV{ - .ty = self.interpreter.ip.indexToKey(result.ty), - .val = if (result.val == .none) null else self.interpreter.ip.indexToKey(result.val), + .ty = ty, + .val = val, }; } @@ -428,8 +443,7 @@ fn testCall( fn testExpr( expr: []const u8, - expected_ty: Key, - expected_val: ?Key, + expected: Key, ) !void { const source = try std.fmt.allocPrint(allocator, \\const foobarbaz = {s}; @@ -441,34 +455,19 @@ fn testExpr( const result = try context.interpret(context.findVar("foobarbaz")); - try expectEqualKey(context.interpreter.ip, expected_ty, result.ty); - if (expected_val) |expected| { - try expectEqualKey(context.interpreter.ip, expected, result.val); - } + try expectEqualKey(context.interpreter.ip, expected, result.val); } -/// TODO refactor this code fn expectEqualKey(ip: InternPool, expected: Key, actual: ?Key) !void { if (actual) |actual_key| { - if (expected.eql(actual_key)) return; - - if (expected.isType() and actual_key.isType()) { - std.debug.print("expected type `{}`, found type `{}`\n", .{ expected.fmtType(ip), actual_key.fmtType(ip) }); - } else if (expected.isType()) { - std.debug.print("expected type `{}`, found value ({})\n", .{ expected.fmtType(ip), actual_key }); - } else if (actual_key.isType()) { - std.debug.print("expected value ({}), found type `{}`\n", .{ expected, actual_key.fmtType(ip) }); - } else { - std.debug.print("expected value ({}), found value ({})\n", .{ expected, actual_key }); // TODO print value + if (!expected.eql(actual_key)) { + std.debug.print("expected `{}`, found `{}`\n", .{ expected.fmt(ip), actual_key.fmt(ip) }); + return error.TestExpectedEqual; } } else { - if (expected.isType()) { - std.debug.print("expected type `{}`, found null\n", .{expected.fmtType(ip)}); - } else { - std.debug.print("expected value ({}), found null\n", .{expected}); - } + std.debug.print("expected `{}`, found null\n", .{expected.fmt(ip)}); + return error.TestExpectedEqual; } - return error.TestExpectedEqual; } fn interpretReportErrors(