diff --git a/build.zig b/build.zig index 5a56791..6f32e43 100644 --- a/build.zig +++ b/build.zig @@ -178,17 +178,19 @@ pub fn build(b: *std.build.Builder) !void { const test_step = b.step("test", "Run all the tests"); test_step.dependOn(b.getInstallStep()); + const test_filter = b.option( + []const u8, + "test-filter", + "Skip tests that do not match filter", + ); + var tests = b.addTest(.{ .root_source_file = .{ .path = "tests/tests.zig" }, .target = target, .optimize = .Debug, }); - tests.setFilter(b.option( - []const u8, - "test-filter", - "Skip tests that do not match filter", - )); + tests.setFilter(test_filter); if (coverage) { const src_dir = b.pathJoin(&.{ build_root_path, "src" }); @@ -218,6 +220,14 @@ pub fn build(b: *std.build.Builder) !void { tests.addModule("diffz", diffz_module); test_step.dependOn(&tests.step); + + var src_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/zls.zig" }, + .target = target, + .optimize = .Debug, + }); + src_tests.setFilter(test_filter); + test_step.dependOn(&src_tests.step); } const CheckSubmodulesStep = struct { diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index bde02db..973d65b 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -4,35 +4,33 @@ // TODO: DODify const std = @import("std"); +const builtin = @import("builtin"); const ast = @import("ast.zig"); const zig = std.zig; const Ast = zig.Ast; const analysis = @import("analysis.zig"); +const offsets = @import("offsets.zig"); const DocumentStore = @import("DocumentStore.zig"); -const ComptimeInterpreter = @This(); + +pub const InternPool = @import("analyser/InternPool.zig"); +pub const Index = InternPool.Index; +pub const Key = InternPool.Key; +pub const ComptimeInterpreter = @This(); const log = std.log.scoped(.comptime_interpreter); -// TODO: Investigate arena - allocator: std.mem.Allocator, +ip: InternPool, document_store: *DocumentStore, uri: DocumentStore.Uri, -root_type: ?Type = null, +namespaces: std.MultiArrayList(Namespace) = .{}, /// Interpreter diagnostic errors errors: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, InterpreterError) = .{}, -// TODO: Deduplicate typeinfo across different interpreters -type_info: std.ArrayListUnmanaged(TypeInfo) = .{}, -type_info_map: std.HashMapUnmanaged(TypeInfo, usize, TypeInfo.Context, std.hash_map.default_max_load_percentage) = .{}, - -// TODO: Use DOD -value_data_list: std.ArrayListUnmanaged(*ValueData) = .{}, - pub fn getHandle(interpreter: *ComptimeInterpreter) *const DocumentStore.Handle { // This interpreter is loaded from a known-valid handle so a valid handle must exist - return interpreter.document_store.getOrLoadHandle(interpreter.uri).?; + return interpreter.document_store.getHandle(interpreter.uri).?; } pub const InterpreterError = struct { @@ -40,523 +38,89 @@ pub const InterpreterError = struct { message: []const u8, }; -/// `message` must be allocated with interpreter allocator -pub fn recordError(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, code: []const u8, message: []const u8) error{OutOfMemory}!void { - try interpreter.errors.put(interpreter.allocator, node_idx, .{ +pub fn recordError( + interpreter: *ComptimeInterpreter, + node_idx: Ast.Node.Index, + code: []const u8, + comptime fmt: []const u8, + args: anytype, +) error{OutOfMemory}!void { + const message = try std.fmt.allocPrint(interpreter.allocator, fmt, args); + errdefer interpreter.allocator.free(message); + const previous = try interpreter.errors.fetchPut(interpreter.allocator, node_idx, .{ .code = code, .message = message, }); + if (previous) |p| interpreter.allocator.free(p.value.message); } pub fn deinit(interpreter: *ComptimeInterpreter) void { - var err_it = interpreter.errors.iterator(); - while (err_it.next()) |entry| interpreter.allocator.free(entry.value_ptr.message); - - if (interpreter.root_type) |rt| rt.getTypeInfo().getScopeOfType().?.deinit(); - for (interpreter.type_info.items) |*ti| ti.deinit(interpreter.allocator); - for (interpreter.value_data_list.items) |ti| interpreter.allocator.destroy(ti); + for (interpreter.errors.values()) |err| { + interpreter.allocator.free(err.message); + } interpreter.errors.deinit(interpreter.allocator); - interpreter.type_info.deinit(interpreter.allocator); - interpreter.type_info_map.deinit(interpreter.allocator); - interpreter.value_data_list.deinit(interpreter.allocator); + interpreter.ip.deinit(interpreter.allocator); + + var i: usize = 0; + while (i < interpreter.namespaces.len) : (i += 1) { + interpreter.namespaces.items(.decls)[i].deinit(interpreter.allocator); + interpreter.namespaces.items(.usingnamespaces)[i].deinit(interpreter.allocator); + } + interpreter.namespaces.deinit(interpreter.allocator); } -pub const TypeInfo = union(enum) { - pub const Context = struct { - interpreter: ComptimeInterpreter, - hasher: *std.hash.Wyhash, - - pub fn hash(self: @This(), s: TypeInfo) u64 { - TypeInfo.hash(self, s); - return self.hasher.final(); - } - pub fn eql(self: @This(), a: TypeInfo, b: TypeInfo) bool { - _ = self; - return TypeInfo.eql(a, b); - } - }; - - pub const Signedness = enum { signed, unsigned }; - - pub const Struct = struct { - /// Declarations contained within - scope: *InterpreterScope, - fields: std.StringHashMapUnmanaged(FieldDefinition) = .{}, - }; - - pub const Int = struct { - bits: u16, - signedness: Signedness, - }; - - pub const Pointer = struct { - size: Size, - is_const: bool, - is_volatile: bool, - child: Type, - is_allowzero: bool, - - sentinel: ?*ValueData, - - pub const Size = enum { - one, - many, - slice, - c, - }; - }; - - pub const Fn = struct { - return_type: ?Type, - /// Index into interpreter.declarations - params: std.ArrayListUnmanaged(usize) = .{}, - }; - - pub const Array = struct { - len: usize, - child: Type, - - sentinel: ?*ValueData, - }; - - /// Hack to get anytype working; only valid on fnparams - @"anytype", - type, - bool, - - @"struct": Struct, - pointer: Pointer, - @"fn": Fn, - - int: Int, - comptime_int, - float: u16, - comptime_float, - - array: Array, - - pub fn eql(a: TypeInfo, b: TypeInfo) bool { - if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false; - return switch (a) { - .@"struct" => false, // Struct declarations can never be equal (this is a lie, gotta fix this) - .pointer => p: { - const ap = a.pointer; - const bp = b.pointer; - break :p ap.size == bp.size and ap.is_const == bp.is_const and ap.is_volatile == bp.is_volatile and eql( - ap.child.getTypeInfo(), - bp.child.getTypeInfo(), - ) and ap.is_allowzero == bp.is_allowzero and ((ap.sentinel == null and bp.sentinel == null) or ((ap.sentinel != null and bp.sentinel != null) and ap.sentinel.?.eql(bp.sentinel.?))); - }, - .int => a.int.signedness == b.int.signedness and a.int.bits == b.int.bits, - .float => a.float == b.float, - else => return true, - }; - } - - pub fn hash(context: TypeInfo.Context, ti: TypeInfo) void { - context.hasher.update(&[_]u8{@enumToInt(ti)}); - return switch (ti) { - .@"struct" => |s| { - _ = s; - // TODO: Fix - // context.hasher.update(std.mem.sliceAsBytes(s.fields.items)); - // TODO: Fix - // context.hasher.update(std.mem.sliceAsBytes(s.declarations.items)); - }, - .pointer => |p| { - // const ap = a.pointer; - // const bp = b.pointer; - context.hasher.update(&[_]u8{ @enumToInt(p.size), @boolToInt(p.is_const), @boolToInt(p.is_volatile) }); - TypeInfo.hash(context, p.child.getTypeInfo()); - context.hasher.update(&[_]u8{@boolToInt(p.is_allowzero)}); - // TODO: Hash Sentinel - // break :p ap.size == bp.size and ap.is_const == bp.is_const and ap.is_volatile == bp.is_volatile and eql( - // source_unit, - // source_interpreter.type_info.items[ap.child.info_idx], - // source_interpreter.type_info.items[bp.child.info_idx], - // ) and ap.is_allowzero == bp.is_allowzero and ((ap.sentinel == null and bp.sentinel == null) or ((ap.sentinel != null and bp.sentinel != null) and ap.sentinel.?.eql(bp.sentinel.?))); - }, - .int => |i| { - // a.int.signedness == b.int.signedness and a.int.bits == b.int.bits; - context.hasher.update(&[_]u8{@enumToInt(i.signedness)}); - context.hasher.update(&std.mem.toBytes(i.bits)); - }, - .float => |f| context.hasher.update(&std.mem.toBytes(f)), - else => {}, - }; - } - - pub fn deinit(ti: *TypeInfo, allocator: std.mem.Allocator) void { - switch (ti.*) { - .@"struct" => |*s| s.fields.deinit(allocator), - else => {}, - } - } - - pub fn getScopeOfType(ti: TypeInfo) ?*InterpreterScope { - return switch (ti) { - .@"struct" => |s| s.scope, - else => null, - }; - } -}; - pub const Type = struct { interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, - info_idx: usize, - - pub fn getTypeInfo(@"type": Type) TypeInfo { - return @"type".interpreter.type_info.items[@"type".info_idx]; - } - - /// Be careful with this; typeinfo resizes reassign pointers! - pub fn getTypeInfoMutable(@"type": Type) *TypeInfo { - return &@"type".interpreter.type_info.items[@"type".info_idx]; - } + ty: Index, }; pub const Value = struct { interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, - type: Type, - value_data: *ValueData, - - pub fn eql(value: Value, other_value: Value) bool { - return value.value_data.eql(other_value.value_data); - } + ty: Index, + val: Index, }; - -pub const ValueData = union(enum) { - // TODO: Support larger ints, floats; bigints? - - type: Type, - bool: bool, - - @"struct": struct {}, - /// This is what a pointer is; we don't need to map - /// this to anything because @ptrToInt is comptime-illegal - /// Pointer equality scares me though :( (but that's for later) - one_ptr: *ValueData, - /// Special case slice; this is extremely common at comptime so it makes sense - slice_of_const_u8: []const u8, - - unsigned_int: u64, - signed_int: i64, - /// If the int does not fit into the previous respective slots, - /// use a bit int to store it - big_int: std.math.big.int.Managed, - - float: f64, - - @"fn", - runtime, - comptime_undetermined, - - pub fn eql(data: *ValueData, other_data: *ValueData) bool { - if (std.meta.activeTag(data.*) != std.meta.activeTag(other_data.*)) return false; - // std.enums. - // std.meta.activeTag(u: anytype) - switch (data.*) { - .bool => return data.bool == other_data.bool, - .big_int => return data.big_int.eq(other_data.big_int), - .unsigned_int => return data.unsigned_int == other_data.unsigned_int, - .signed_int => return data.signed_int == other_data.signed_int, - .float => return data.float == other_data.float, - - else => return false, - } - } - - /// Get the bit count required to store a certain integer - pub fn bitCount(data: ValueData) ?u16 { - return switch (data) { - // TODO: Implement for signed ints - .unsigned_int => |i| if (i == 0) 0 else std.math.log2_int_ceil(@TypeOf(i), i + 1), - .big_int => |bi| @intCast(u16, bi.bitCountAbs()), - else => null, - }; - } -}; - -pub const FieldDefinition = struct { - node_idx: Ast.Node.Index, - /// Store name so tree doesn't need to be used to access field name - /// When the field is a tuple field, `name` will be an empty slice - name: []const u8, - type: Type, - default_value: ?Value, -}; - -pub const Declaration = struct { - scope: *InterpreterScope, - - node_idx: Ast.Node.Index, - /// Store name so tree doesn't need to be used to access declaration name - name: []const u8, - - /// If value is null, declaration has not been interpreted yet - value: ?Value = null, - - // TODO: figure this out - // pub const DeclarationKind = enum{variable, function}; - // pub fn declarationKind(declaration: Declaration, tree: Ast) DeclarationKind { - // return switch(tree.nodes.items(.tag)[declaration.node_idx]) { - // .fn_proto, - // .fn_proto_one, - // .fn_proto_simple, - // .fn_proto_multi, - // .fn_decl - // } - // } - - pub fn getValue(decl: *Declaration) InterpretError!Value { - var interpreter = decl.scope.interpreter; - const tree = decl.scope.interpreter.getHandle().tree; - const tags = tree.nodes.items(.tag); - - if (decl.value == null) { - switch (tags[decl.node_idx]) { - .global_var_decl, - .local_var_decl, - .aligned_var_decl, - .simple_var_decl, - => { - const var_decl = tree.fullVarDecl(decl.node_idx).?; - if (var_decl.ast.init_node == 0) - return error.CriticalAstFailure; - - var value = try (try interpreter.interpret(var_decl.ast.init_node, decl.scope, .{})).getValue(); - - if (var_decl.ast.type_node != 0) { - var type_val = try (try interpreter.interpret(var_decl.ast.type_node, decl.scope, .{})).getValue(); - if (type_val.type.getTypeInfo() != .type) { - try interpreter.recordError( - decl.node_idx, - "expected_type", - std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(type_val.type.getTypeInfo())}) catch return error.CriticalAstFailure, - ); - return error.InvalidCast; - } - value = try interpreter.cast(var_decl.ast.type_node, type_val.value_data.type, value); - } - - decl.value = value; - }, - else => @panic("No other case supported for lazy declaration evaluation"), - } - } - - return decl.value.?; - } - - pub fn isConstant(declaration: Declaration) bool { - const tree = declaration.scope.interpreter.getHandle().tree; - return switch (tree.nodes.items(.tag)[declaration.node_idx]) { - .global_var_decl, - .local_var_decl, - .aligned_var_decl, - .simple_var_decl, - => { - return tree.tokenSlice(tree.fullVarDecl(declaration.node_idx).?.ast.mut_token).len != 3; - }, - else => false, - }; - } -}; - -pub fn createType(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, type_info: TypeInfo) std.mem.Allocator.Error!Type { - // TODO: Figure out dedup - var hasher = std.hash.Wyhash.init(0); - var gpr = try interpreter.type_info_map.getOrPutContext(interpreter.allocator, type_info, .{ .interpreter = interpreter.*, .hasher = &hasher }); - - if (gpr.found_existing) { - return Type{ .interpreter = interpreter, .node_idx = node_idx, .info_idx = gpr.value_ptr.* }; - } else { - try interpreter.type_info.append(interpreter.allocator, type_info); - const info_idx = interpreter.type_info.items.len - 1; - gpr.value_ptr.* = info_idx; - return Type{ .interpreter = interpreter, .node_idx = node_idx, .info_idx = info_idx }; - } -} - -pub fn createValueData(interpreter: *ComptimeInterpreter, data: ValueData) error{OutOfMemory}!*ValueData { - var vd = try interpreter.allocator.create(ValueData); - try interpreter.value_data_list.append(interpreter.allocator, vd); - vd.* = data; - return vd; -} - -pub const TypeInfoFormatter = struct { - interpreter: *const ComptimeInterpreter, - ti: TypeInfo, - - pub fn format(value: TypeInfoFormatter, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - return switch (value.ti) { - .int => |ii| switch (ii.signedness) { - .signed => try writer.print("i{d}", .{ii.bits}), - .unsigned => try writer.print("u{d}", .{ii.bits}), - }, // TODO - .float => |f| try writer.print("f{d}", .{f}), - .comptime_int => try writer.writeAll("comptime_int"), - .comptime_float => try writer.writeAll("comptime_float"), - .type => try writer.writeAll("type"), - .bool => try writer.writeAll("bool"), - .@"struct" => |s| { - try writer.writeAll("struct {"); - var field_iterator = s.fields.iterator(); - while (field_iterator.next()) |di| { - try writer.print("{s}: {s}, ", .{ di.key_ptr.*, value.interpreter.formatTypeInfo(di.value_ptr.*.type.getTypeInfo()) }); - } - - var iterator = s.scope.declarations.iterator(); - while (iterator.next()) |di| { - const decl = di.value_ptr; - if (decl.isConstant()) { - if (decl.value) |sv| { - try writer.print("const {s}: {any} = { }, ", .{ - decl.name, - value.interpreter.formatTypeInfo(sv.type.getTypeInfo()), - value.interpreter.formatValue(sv), - }); - } else { - try writer.print("const {s} (not analyzed), ", .{decl.name}); - } - } else { - if (decl.value) |sv| { - try writer.print("var {s}: {any} = { }, ", .{ - decl.name, - value.interpreter.formatTypeInfo(sv.type.getTypeInfo()), - value.interpreter.formatValue(sv), - }); - } else { - try writer.print("var {s} (not analyzed), ", .{decl.name}); - } - } - } - try writer.writeAll("}"); - }, - else => try writer.print("UnimplementedTypeInfoPrint", .{}), - }; - } -}; - -pub fn formatTypeInfo(interpreter: *const ComptimeInterpreter, ti: TypeInfo) TypeInfoFormatter { - return TypeInfoFormatter{ .interpreter = interpreter, .ti = ti }; -} - -pub const ValueFormatter = struct { - interpreter: *const ComptimeInterpreter, - val: Value, - - pub fn format(form: ValueFormatter, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = fmt; - _ = options; - - var value = form.val; - var ti = value.type.getTypeInfo(); - - return switch (ti) { - .int, .comptime_int => switch (value.value_data.*) { - .unsigned_int => |a| try writer.print("{d}", .{a}), - .signed_int => |a| try writer.print("{d}", .{a}), - .big_int => |a| try writer.print("{d}", .{a}), - - else => unreachable, - }, - .type => try writer.print("{ }", .{form.interpreter.formatTypeInfo(value.value_data.type.getTypeInfo())}), - else => try writer.print("UnimplementedValuePrint", .{}), - }; - } -}; - -pub fn formatValue(interpreter: *const ComptimeInterpreter, value: Value) ValueFormatter { - return ValueFormatter{ .interpreter = interpreter, .val = value }; -} - // pub const Comptimeness = enum { @"comptime", runtime }; -pub const InterpreterScope = struct { - interpreter: *ComptimeInterpreter, +pub const Namespace = struct { + /// always points to Namespace or Index.none + parent: Namespace.Index, + node_idx: Ast.Node.Index, + /// Will be a struct, enum, union, opaque or .none + ty: InternPool.Index, + decls: std.StringArrayHashMapUnmanaged(InternPool.DeclIndex) = .{}, + usingnamespaces: std.ArrayListUnmanaged(InternPool.DeclIndex) = .{}, + + pub const Index = InternPool.NamespaceIndex; // TODO: Actually use this value // comptimeness: Comptimeness, - parent: ?*InterpreterScope = null, - node_idx: Ast.Node.Index, - declarations: std.StringHashMapUnmanaged(Declaration) = .{}, - /// Resizes can modify element pointer locations, so we use a list of pointers - child_scopes: std.ArrayListUnmanaged(*InterpreterScope) = .{}, - - pub const ScopeKind = enum { container, block, function }; - pub fn scopeKind(scope: InterpreterScope) ScopeKind { - const tree = scope.interpreter.getHandle().tree; - return if (ast.isContainer(tree, scope.node_idx)) .container else .block; - } - - pub fn getLabel(scope: InterpreterScope) ?Ast.TokenIndex { - const tree = scope.interpreter.getHandle().tree; + pub fn getLabel(self: Namespace, tree: Ast) ?Ast.TokenIndex { const token_tags = tree.tokens.items(.tag); - return switch (scope.scopeKind()) { - .block => z: { - const lbrace = tree.nodes.items(.main_token)[scope.node_idx]; - break :z if (token_tags[lbrace - 1] == .colon and token_tags[lbrace - 2] == .identifier) - lbrace - 2 - else - null; + switch (tree.nodes.items(.tag)[self.node_idx]) { + .block_two, + .block_two_semicolon, + .block, + .block_semicolon, + => { + const lbrace = tree.nodes.items(.main_token)[self.node_idx]; + if (token_tags[lbrace - 1] == .colon and token_tags[lbrace - 2] == .identifier) { + return lbrace - 2; + } + + return null; }, - else => null, - }; - } - - pub const ParentScopeIterator = struct { - maybe_scope: ?*InterpreterScope, - - pub fn next(psi: *ParentScopeIterator) ?*InterpreterScope { - if (psi.maybe_scope) |scope| { - const curr = scope; - psi.maybe_scope = scope.parent; - return curr; - } else return null; + else => return null, } - }; - - pub fn parentScopeIterator(scope: *InterpreterScope) ParentScopeIterator { - return ParentScopeIterator{ .maybe_scope = scope }; - } - - pub fn deinit(scope: *InterpreterScope) void { - const allocator = scope.interpreter.allocator; - - scope.declarations.deinit(allocator); - for (scope.child_scopes.items) |child| child.deinit(); - scope.child_scopes.deinit(allocator); - - allocator.destroy(scope); } }; -pub fn newScope( - interpreter: *ComptimeInterpreter, - maybe_parent: ?*InterpreterScope, - node_idx: Ast.Node.Index, -) std.mem.Allocator.Error!*InterpreterScope { - var ls = try interpreter.allocator.create(InterpreterScope); - if (maybe_parent) |parent| try parent.child_scopes.append(interpreter.allocator, ls); - ls.* = .{ - .interpreter = interpreter, - .parent = maybe_parent, - .node_idx = node_idx, - }; - return ls; -} - pub const InterpretResult = union(enum) { @"break": ?[]const u8, break_with_value: struct { @@ -582,115 +146,25 @@ pub const InterpretResult = union(enum) { } }; -fn getDeclCount(tree: Ast, node_idx: Ast.Node.Index) usize { - var buffer: [2]Ast.Node.Index = undefined; - const container_decl = tree.fullContainerDecl(&buffer, node_idx).?; - - var count: usize = 0; - for (container_decl.ast.members) |member| { - switch (tree.nodes.items(.tag)[member]) { - .global_var_decl, - .local_var_decl, - .aligned_var_decl, - .simple_var_decl, - => count += 1, - else => {}, - } - } - - return count; -} - pub fn huntItDown( interpreter: *ComptimeInterpreter, - scope: *InterpreterScope, + namespace: Namespace.Index, decl_name: []const u8, options: InterpretOptions, -) InterpretError!*Declaration { - const tree = interpreter.getHandle().tree; - const tags = tree.nodes.items(.tag); +) ?InternPool.DeclIndex { + _ = options; - var psi = scope.parentScopeIterator(); - while (psi.next()) |pscope| { - const known_decl = pscope.declarations.getEntry(decl_name); - if (pscope.scopeKind() == .container and - known_decl == null and - pscope.declarations.count() != getDeclCount(tree, pscope.node_idx)) - { - log.info("Order-independent evaluating {s}...", .{decl_name}); + var current_namespace = namespace; + while (current_namespace != .none) { + const decls = interpreter.namespaces.items(.decls)[@enumToInt(current_namespace)]; + defer current_namespace = interpreter.namespaces.items(.parent)[@enumToInt(current_namespace)]; - var buffer: [2]Ast.Node.Index = undefined; - const container_decl = tree.fullContainerDecl(&buffer, pscope.node_idx).?; - - for (container_decl.ast.members) |member| { - switch (tags[member]) { - .global_var_decl, - .local_var_decl, - .aligned_var_decl, - .simple_var_decl, - => { - if (std.mem.eql(u8, analysis.getDeclName(tree, member).?, decl_name)) { - _ = try interpreter.interpret(member, pscope, options); - return pscope.declarations.getEntry(decl_name).?.value_ptr; - } - }, - else => {}, - } - } + if (decls.get(decl_name)) |decl| { + return decl; } - return (known_decl orelse continue).value_ptr; } - log.err("Identifier not found: {s}", .{decl_name}); - return error.IdentifierNotFound; -} - -pub fn cast( - interpreter: *ComptimeInterpreter, - node_idx: Ast.Node.Index, - dest_type: Type, - value: Value, -) error{ OutOfMemory, InvalidCast }!Value { - const value_data = value.value_data; - - const to_type_info = dest_type.getTypeInfo(); - const from_type_info = value.type.getTypeInfo(); - - // TODO: Implement more implicit casts - - if (from_type_info.eql(to_type_info)) return value; - - const err = switch (from_type_info) { - .comptime_int => switch (to_type_info) { - .int => { - if (value_data.bitCount().? > to_type_info.int.bits) { - switch (value_data.*) { - .unsigned_int => |bi| try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "integer value {d} cannot be coerced to type '{s}'", .{ bi, interpreter.formatTypeInfo(to_type_info) })), - .signed_int => |bi| try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "integer value {d} cannot be coerced to type '{s}'", .{ bi, interpreter.formatTypeInfo(to_type_info) })), - .big_int => |bi| try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "integer value {d} cannot be coerced to type '{s}'", .{ bi, interpreter.formatTypeInfo(to_type_info) })), - - else => unreachable, - } - return error.InvalidCast; - } - }, - else => error.InvalidCast, - }, - else => error.InvalidCast, - }; - - err catch |e| { - try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "invalid cast from '{s}' to '{s}'", .{ interpreter.formatTypeInfo(from_type_info), interpreter.formatTypeInfo(to_type_info) })); - return e; - }; - - return Value{ - .interpreter = interpreter, - - .node_idx = node_idx, - .type = dest_type, - .value_data = value.value_data, - }; + return null; } // Might be useful in the future @@ -708,10 +182,11 @@ pub const InterpretError = std.mem.Allocator.Error || std.fmt.ParseIntError || s ImportFailure, InvalidCast, }; + pub fn interpret( interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, - scope: ?*InterpreterScope, + namespace: Namespace.Index, options: InterpretOptions, ) InterpretError!InterpretResult { const tree = interpreter.getHandle().tree; @@ -734,68 +209,70 @@ pub fn interpret( // .tagged_union_enum_tag_trailing, .root, => { - var container_scope = try interpreter.newScope(scope, node_idx); - var type_info = TypeInfo{ - .@"struct" = .{ - .scope = container_scope, - }, - }; - var cont_type = try interpreter.createType(node_idx, type_info); + try interpreter.namespaces.append(interpreter.allocator, .{ + .parent = namespace, + .node_idx = node_idx, + .ty = .none, + }); + const container_namespace = @intToEnum(Namespace.Index, interpreter.namespaces.len - 1); - if (node_idx == 0) interpreter.root_type = cont_type; + const struct_index = try interpreter.ip.createStruct(interpreter.allocator, .{ + .fields = .{}, + .namespace = container_namespace, + .layout = .Auto, // TODO + .backing_int_ty = .none, // TODO + .status = .none, // TODO + }); + var struct_info = interpreter.ip.getStruct(struct_index); var buffer: [2]Ast.Node.Index = undefined; - const container_decl = tree.fullContainerDecl(&buffer, node_idx).?; + const container_decl = tree.fullContainerDecl(&buffer, node_idx).?; for (container_decl.ast.members) |member| { const container_field = tree.fullContainerField(member) orelse { - _ = try interpreter.interpret(member, container_scope, options); + _ = try interpreter.interpret(member, container_namespace, options); continue; }; - var init_type_value = try (try interpreter.interpret(container_field.ast.type_expr, container_scope, .{})).getValue(); - var default_value = if (container_field.ast.value_expr == 0) - null - else - try (try interpreter.interpret(container_field.ast.value_expr, container_scope, .{})).getValue(); + var init_type_value = try (try interpreter.interpret(container_field.ast.type_expr, container_namespace, .{})).getValue(); - if (init_type_value.type.getTypeInfo() != .type) { + 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 + + if (init_type_value.ty != Index.type_type) { try interpreter.recordError( container_field.ast.type_expr, "expected_type", - try std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(init_type_value.type.getTypeInfo())}), + "expected type 'type', found '{}'", + .{init_type_value.ty.fmtType(interpreter.ip)}, ); continue; } - const name = if (container_field.ast.tuple_like) - &[0]u8{} - else - tree.tokenSlice(container_field.ast.main_token); - const field = FieldDefinition{ - .node_idx = member, - .name = name, - .type = init_type_value.value_data.type, - .default_value = default_value, - // TODO: Default values - // .@"type" = T: { - // var value = (try interpreter.interpret(container_field.ast.type_expr, scope_idx, true)).?.value; - // break :T @ptrCast(*Type, @alignCast(@alignOf(*Type), value)).*; - // }, - // .value = null, - }; + const field_name = tree.tokenSlice(container_field.ast.main_token); - try cont_type.getTypeInfoMutable().@"struct".fields.put(interpreter.allocator, name, field); + try struct_info.fields.put(interpreter.allocator, field_name, .{ + .ty = init_type_value.val, + .default_value = default_value, + .alignment = 0, // TODO, + .is_comptime = false, // TODO + }); } + const struct_type = try interpreter.ip.get(interpreter.allocator, Key{ .struct_type = struct_index }); + interpreter.namespaces.items(.ty)[@enumToInt(container_namespace)] = struct_type; + return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .type = {} }), - .value_data = try interpreter.createValueData(.{ .type = cont_type }), + .ty = Index.type_type, + .val = struct_type, } }; }, .error_set_decl => { + // TODO return InterpretResult{ .nothing = {} }; }, .global_var_decl, @@ -803,26 +280,53 @@ pub fn interpret( .aligned_var_decl, .simple_var_decl, => { - // TODO: Add 0 check + var decls: *std.StringArrayHashMapUnmanaged(InternPool.DeclIndex) = &interpreter.namespaces.items(.decls)[@enumToInt(namespace)]; + const name = analysis.getDeclName(tree, node_idx).?; - if (scope.?.declarations.contains(name)) - return InterpretResult{ .nothing = {} }; - - const decl = tree.fullVarDecl(node_idx).?; - if (decl.ast.init_node == 0) - return InterpretResult{ .nothing = {} }; - - try scope.?.declarations.put(interpreter.allocator, name, .{ - .scope = scope.?, - .node_idx = node_idx, + const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ .name = name, + .node_idx = node_idx, + .ty = .none, + .val = .none, + .alignment = 0, // TODO + .address_space = .generic, // TODO + .is_pub = true, // TODO + .is_exported = false, // TODO }); + const gop = try decls.getOrPutValue(interpreter.allocator, name, decl_index); + if (gop.found_existing) { + return InterpretResult{ .nothing = {} }; + } + + const var_decl = tree.fullVarDecl(node_idx).?; + + const type_value = blk: { + if (var_decl.ast.type_node == 0) break :blk null; + const result = interpreter.interpret(var_decl.ast.type_node, namespace, .{}) catch break :blk null; + break :blk result.maybeGetValue(); + }; + const init_value = blk: { + if (var_decl.ast.init_node == 0) break :blk null; + const result = interpreter.interpret(var_decl.ast.init_node, namespace, .{}) catch break :blk null; + break :blk result.maybeGetValue(); + }; + + if (type_value == null and init_value == null) return InterpretResult{ .nothing = {} }; + + if (type_value) |v| { + if (v.ty != Index.type_type) return InterpretResult{ .nothing = {} }; + } + + 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; + // TODO: Am I a dumbo shrimp? (e.g. is this tree shaking correct? works on my machine so like...) // if (scope.?.scopeKind() != .container) { - if (scope.?.node_idx != 0) - _ = try scope.?.declarations.getPtr(name).?.getValue(); + // if (scope.?.node_idx != 0) + // _ = try decls.getPtr(name).?.getValue(); return InterpretResult{ .nothing = {} }; }, @@ -831,22 +335,21 @@ pub fn interpret( .block_two, .block_two_semicolon, => { - // try interpreter.scopes.append(interpreter.allocator, .{ - // .node_idx = node_idx, - // .parent_scope = parent_scope_idx orelse std.math.maxInt(usize), - // }); - // const scope_idx = interpreter.scopes.items.len - 1; - - var block_scope = try interpreter.newScope(scope, node_idx); + try interpreter.namespaces.append(interpreter.allocator, .{ + .parent = namespace, + .node_idx = node_idx, + .ty = .none, + }); + const block_namespace = @intToEnum(Namespace.Index, interpreter.namespaces.len - 1); var buffer: [2]Ast.Node.Index = undefined; const statements = ast.blockStatements(tree, node_idx, &buffer).?; for (statements) |idx| { - const ret = try interpreter.interpret(idx, block_scope, options); + const ret = try interpreter.interpret(idx, block_namespace, options); switch (ret) { .@"break" => |lllll| { - const maybe_block_label_string = if (scope.?.getLabel()) |i| tree.tokenSlice(i) else null; + const maybe_block_label_string = if (interpreter.namespaces.get(@enumToInt(namespace)).getLabel(tree)) |i| tree.tokenSlice(i) else null; if (lllll) |l| { if (maybe_block_label_string) |ls| { if (std.mem.eql(u8, l, ls)) { @@ -858,7 +361,7 @@ pub fn interpret( } }, .break_with_value => |bwv| { - const maybe_block_label_string = if (scope.?.getLabel()) |i| tree.tokenSlice(i) else null; + const maybe_block_label_string = if (interpreter.namespaces.get(@enumToInt(namespace)).getLabel(tree)) |i| tree.tokenSlice(i) else null; if (bwv.label) |l| { if (maybe_block_label_string) |ls| { @@ -878,160 +381,349 @@ pub fn interpret( return InterpretResult{ .nothing = {} }; }, .identifier => { - var value = tree.getNodeSource(node_idx); + const identifier = offsets.nodeToSlice(tree, node_idx); - if (std.mem.eql(u8, "bool", value)) return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .type = {} }), - .value_data = try interpreter.createValueData(.{ .type = try interpreter.createType(node_idx, .{ .bool = {} }) }), - } }; - if (std.mem.eql(u8, "true", value)) return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .bool = {} }), - .value_data = try interpreter.createValueData(.{ .bool = true }), - } }; - if (std.mem.eql(u8, "false", value)) return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .bool = {} }), - .value_data = try interpreter.createValueData(.{ .bool = false }), - } }; + const simples = std.ComptimeStringMap(Index, .{ + .{ "anyerror", Index.anyerror_type }, + .{ "anyframe", Index.anyframe_type }, + .{ "anyopaque", Index.anyopaque_type }, + .{ "bool", Index.bool_type }, + .{ "c_int", Index.c_int_type }, + .{ "c_long", Index.c_long_type }, + .{ "c_longdouble", Index.c_longdouble_type }, + .{ "c_longlong", Index.c_longlong_type }, + .{ "c_short", Index.c_short_type }, + .{ "c_uint", Index.c_uint_type }, + .{ "c_ulong", Index.c_ulong_type }, + .{ "c_ulonglong", Index.c_ulonglong_type }, + .{ "c_ushort", Index.c_ushort_type }, + .{ "comptime_float", Index.comptime_float_type }, + .{ "comptime_int", Index.comptime_int_type }, + .{ "f128", Index.f128_type }, + .{ "f16", Index.f16_type }, + .{ "f32", Index.f32_type }, + .{ "f64", Index.f64_type }, + .{ "f80", Index.f80_type }, + .{ "false", Index.bool_false }, + .{ "isize", Index.isize_type }, + .{ "noreturn", Index.noreturn_type }, + .{ "null", Index.null_value }, + .{ "true", Index.bool_true }, + .{ "type", Index.type_type }, + .{ "undefined", Index.undefined_value }, + .{ "usize", Index.usize_type }, + .{ "void", Index.void_type }, + }); - if (value.len == 5 and (value[0] == 'u' or value[0] == 'i') and std.mem.eql(u8, "size", value[1..])) return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .type = {} }), - .value_data = try interpreter.createValueData(.{ - .type = try interpreter.createType(node_idx, .{ - .int = .{ - .signedness = if (value[0] == 'u') .unsigned else .signed, - .bits = 64, // TODO: Platform specific - }, - }), - }), - }, - }; - - if (std.mem.eql(u8, "type", value)) { + 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, - .type = try interpreter.createType(node_idx, .{ .type = {} }), - .value_data = try interpreter.createValueData(.{ .type = try interpreter.createType(node_idx, .{ .type = {} }) }), - } }; - } else if (value.len >= 2 and (value[0] == 'u' or value[0] == 'i')) int: { - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .type = {} }), - .value_data = try interpreter.createValueData(.{ .type = try interpreter.createType(node_idx, .{ - .int = .{ - .signedness = if (value[0] == 'u') .unsigned else .signed, - .bits = std.fmt.parseInt(u16, value[1..], 10) catch break :int, - }, - }) }), + .ty = ty, + .val = index, } }; } - // TODO: Floats + if (identifier.len >= 2 and (identifier[0] == 'u' or identifier[0] == 'i')) blk: { + return InterpretResult{ .value = Value{ + .interpreter = interpreter, + .node_idx = node_idx, + .ty = Index.type_type, + .val = 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, + } }), + } }; + } // Logic to find identifiers in accessible scopes - return InterpretResult{ .value = try (interpreter.huntItDown(scope.?, value, options) catch |err| { - if (err == error.IdentifierNotFound) try interpreter.recordError( - node_idx, - "undeclared_identifier", - try std.fmt.allocPrint(interpreter.allocator, "use of undeclared identifier '{s}'", .{value}), - ); - return err; - }).getValue() }; + if (interpreter.huntItDown(namespace, identifier, options)) |decl_index| { + const decl = interpreter.ip.getDecl(decl_index); + if (decl.ty == .none) return InterpretResult{ .nothing = {} }; + return InterpretResult{ .value = Value{ + .interpreter = interpreter, + .node_idx = decl.node_idx, + .ty = decl.ty, + .val = decl.val, + } }; + } + + try interpreter.recordError( + node_idx, + "undeclared_identifier", + "use of undeclared identifier '{s}'", + .{identifier}, + ); + // return error.IdentifierNotFound; + return InterpretResult{ .nothing = {} }; }, .field_access => { if (data[node_idx].rhs == 0) return error.CriticalAstFailure; - const rhs_str = tree.tokenSlice(data[node_idx].rhs); + const field_name = tree.tokenSlice(data[node_idx].rhs); - var ir = try interpreter.interpret(data[node_idx].lhs, scope, options); + var ir = try interpreter.interpret(data[node_idx].lhs, namespace, options); var irv = try ir.getValue(); - var sub_scope = irv.value_data.type.getTypeInfo().getScopeOfType() orelse return error.IdentifierNotFound; - var scope_sub_decl = sub_scope.interpreter.huntItDown(sub_scope, rhs_str, options) catch |err| { - if (err == error.IdentifierNotFound) try interpreter.recordError( - node_idx, - "undeclared_identifier", - try std.fmt.allocPrint(interpreter.allocator, "use of undeclared identifier '{s}'", .{rhs_str}), - ); - return err; + 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, }; - return InterpretResult{ - .value = try scope_sub_decl.getValue(), + const can_have_fields: bool = switch (inner_lhs) { + .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| { + const decl = interpreter.ip.getDecl(decl_index); + return InterpretResult{ .value = Value{ + .interpreter = interpreter, + .node_idx = node_idx, + .ty = decl.ty, + .val = decl.val, + } }; + } + + switch (ty_key) { + .error_set_type => |error_set_info| { // TODO + _ = error_set_info; + }, + .union_type => {}, // TODO + .enum_type => |enum_index| { // TODO + const enum_info = interpreter.ip.getEnum(enum_index); + if (enum_info.fields.get(field_name)) |field| { + _ = field; + return InterpretResult{ + .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + .ty = irv.val, + .val = .none, // TODO resolve enum value + }, + }; + } + }, + else => break :blk false, + } + break :blk true; + }, + else => false, + }, + .pointer_type => |pointer_info| blk: { + if (pointer_info.size == .Slice) { + if (std.mem.eql(u8, field_name, "ptr")) { + var many_ptr_info = InternPool.Key{ .pointer_type = pointer_info }; + many_ptr_info.pointer_type.size = .Many; + return InterpretResult{ + .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 + }, + }; + } else if (std.mem.eql(u8, field_name, "len")) { + return InterpretResult{ + .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + .ty = Index.usize_type, + .val = .none, // TODO resolve length of Slice + }, + }; + } + } else if (interpreter.ip.indexToKey(pointer_info.elem_type) == .array_type) { + if (std.mem.eql(u8, field_name, "len")) { + return InterpretResult{ + .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + .ty = Index.usize_type, + .val = .none, // TODO resolve length of Slice + }, + }; + } + } + 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, + } }; + } + break :blk true; + }, + .optional_type => |optional_info| blk: { + if (!std.mem.eql(u8, field_name, "?")) break :blk false; + if (irv.val == Index.null_value) { + try interpreter.recordError( + node_idx, + "null_unwrap", + "tried to unwrap optional of type `{}` which was null", + .{irv.ty.fmtType(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, + } }; + } + }, + .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]; + }; + + return InterpretResult{ .value = Value{ + .interpreter = interpreter, + .node_idx = data[node_idx].rhs, + .ty = field.ty, + .val = val, + } }; + } + break :blk true; + }, + .enum_type => |enum_info| blk: { // TODO + _ = enum_info; + break :blk true; + }, + .union_type => |union_info| blk: { // TODO + _ = union_info; + break :blk true; + }, + else => false, }; + + 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)}, + ); + } + } + return error.InvalidOperation; }, .grouped_expression => { - return try interpreter.interpret(data[node_idx].lhs, scope, options); + return try interpreter.interpret(data[node_idx].lhs, namespace, options); }, .@"break" => { const label = if (data[node_idx].lhs == 0) null else tree.tokenSlice(data[node_idx].lhs); return if (data[node_idx].rhs == 0) InterpretResult{ .@"break" = label } else - InterpretResult{ .break_with_value = .{ .label = label, .value = try (try interpreter.interpret(data[node_idx].rhs, scope, options)).getValue() } }; + InterpretResult{ .break_with_value = .{ .label = label, .value = try (try interpreter.interpret(data[node_idx].rhs, namespace, options)).getValue() } }; }, .@"return" => { return if (data[node_idx].lhs == 0) InterpretResult{ .@"return" = {} } else - InterpretResult{ .return_with_value = try (try interpreter.interpret(data[node_idx].lhs, scope, options)).getValue() }; + InterpretResult{ .return_with_value = try (try interpreter.interpret(data[node_idx].lhs, namespace, options)).getValue() }; }, .@"if", .if_simple, => { - const if_node = ast.fullIf(tree, node_idx).?; + const if_info = ast.fullIf(tree, node_idx).?; // TODO: Don't evaluate runtime ifs // if (options.observe_values) { - const ir = try interpreter.interpret(if_node.ast.cond_expr, scope, options); - if ((try ir.getValue()).value_data.bool) { - return try interpreter.interpret(if_node.ast.then_expr, scope, options); + 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) { + return try interpreter.interpret(if_info.ast.then_expr, namespace, options); } else { - if (if_node.ast.else_expr != 0) { - return try interpreter.interpret(if_node.ast.else_expr, scope, options); + if (if_info.ast.else_expr != 0) { + return try interpreter.interpret(if_info.ast.else_expr, namespace, options); } else return InterpretResult{ .nothing = {} }; } }, .equal_equal => { - var a = try interpreter.interpret(data[node_idx].lhs, scope, options); - var b = try interpreter.interpret(data[node_idx].rhs, scope, options); - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .bool = {} }), - .value_data = try interpreter.createValueData(.{ .bool = (try a.getValue()).eql(try b.getValue()) }), - } }; - // a.getValue().eql(b.getValue()) + var a = try interpreter.interpret(data[node_idx].lhs, namespace, options); + var b = try interpreter.interpret(data[node_idx].rhs, namespace, options); + const a_value = a.maybeGetValue() orelse return InterpretResult{ .nothing = {} }; + const b_value = b.maybeGetValue() orelse return InterpretResult{ .nothing = {} }; + return InterpretResult{ + .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? + }, + }; }, .number_literal => { const s = tree.getNodeSource(node_idx); const nl = std.zig.parseNumberLiteral(s); - return InterpretResult{ - .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .comptime_int = {} }), - .value_data = try interpreter.createValueData(switch (nl) { - .float => .{ .float = try std.fmt.parseFloat(f64, s) }, - .int => if (s[0] == '-') ValueData{ .signed_int = try std.fmt.parseInt(i64, s, 0) } else ValueData{ .unsigned_int = try std.fmt.parseInt(u64, s, 0) }, - .big_int => |bii| ppp: { - var bi = try std.math.big.int.Managed.init(interpreter.allocator); - try bi.setString(@enumToInt(bii), s[if (bii != .decimal) @as(usize, 2) else @as(usize, 0)..]); - break :ppp .{ .big_int = bi }; - }, - .failure => return error.CriticalAstFailure, - }), + if (nl == .failure) return error.CriticalAstFailure; + + const number_type = if (nl == .float) Index.comptime_float_type else Index.comptime_int_type; + + const value = try interpreter.ip.get( + interpreter.allocator, + switch (nl) { + .float => Key{ + .float_128_value = try std.fmt.parseFloat(f128, s), + }, + .int => if (s[0] == '-') Key{ + .int_i64_value = try std.fmt.parseInt(i64, s, 0), + } else Key{ + .int_u64_value = 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() }; + }, + .failure => return error.CriticalAstFailure, }, - }; + ); + + return InterpretResult{ .value = Value{ + .interpreter = interpreter, + .node_idx = node_idx, + .ty = number_type, + .val = value, + } }; }, .assign, .assign_bit_and, @@ -1051,15 +743,16 @@ pub fn interpret( // TODO: Actually consider operators if (std.mem.eql(u8, tree.getNodeSource(data[node_idx].lhs), "_")) { - _ = try interpreter.interpret(data[node_idx].rhs, scope.?, options); + _ = try interpreter.interpret(data[node_idx].rhs, namespace, options); return InterpretResult{ .nothing = {} }; } - var ir = try interpreter.interpret(data[node_idx].lhs, scope, options); + 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, scope.?, options)).getValue()); + var from_value = (try (try interpreter.interpret(data[node_idx].rhs, namespace, options)).getValue()); - to_value.value_data.* = (try interpreter.cast(node_idx, to_value.type, from_value)).value_data.*; + // TODO report error + _ = try interpreter.ip.cast(interpreter.allocator, to_value.ty, from_value.ty, builtin.target); return InterpretResult{ .nothing = {} }; }, @@ -1093,22 +786,23 @@ pub fn interpret( try writer.writeAll("log: "); for (params) |param, index| { - var value = (try interpreter.interpret(param, scope, options)).maybeGetValue() orelse { + var value = (try interpreter.interpret(param, namespace, options)).maybeGetValue() orelse { try writer.writeAll("indeterminate"); continue; }; - try writer.print("@as({s}, {s})", .{ interpreter.formatTypeInfo(value.type.getTypeInfo()), interpreter.formatValue(value) }); + try writer.print("@as({}, {})", .{ value.ty.fmtType(interpreter.ip), value.val.fmtValue(value.ty, interpreter.ip) }); if (index != params.len - 1) try writer.writeAll(", "); } - try interpreter.recordError(node_idx, "compile_log", try final.toOwnedSlice()); + try interpreter.recordError(node_idx, "compile_log", "{s}", .{try final.toOwnedSlice()}); return InterpretResult{ .nothing = {} }; } if (std.mem.eql(u8, call_name, "@compileError")) { - // TODO: Add message - try interpreter.recordError(node_idx, "compile_error", try std.fmt.allocPrint(interpreter.allocator, "compile error", .{})); + if (params.len != 0) return error.InvalidBuiltin; + const message = offsets.nodeToSlice(tree, params[0]); + try interpreter.recordError(node_idx, "compile_error", "{s}", .{message}); return InterpretResult{ .@"return" = {} }; } @@ -1123,11 +817,18 @@ pub fn interpret( // TODO: Implement root support if (std.mem.eql(u8, import_str[1 .. import_str.len - 1], "root")) { + const struct_index = try interpreter.ip.createStruct(interpreter.allocator, .{ + .fields = .{}, + .namespace = .none, + .layout = .Auto, + .backing_int_ty = .none, + .status = .none, + }); return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .@"struct" = .{ .scope = try interpreter.newScope(null, 0) } }), - .value_data = try interpreter.createValueData(.{ .@"struct" = .{} }), + .ty = try interpreter.ip.get(interpreter.allocator, Key{ .struct_type = struct_index }), + .val = Index.undefined_value, } }; } @@ -1135,112 +836,128 @@ pub fn interpret( defer interpreter.allocator.free(import_uri); var handle = interpreter.document_store.getOrLoadHandle(import_uri) orelse return error.ImportFailure; - try interpreter.document_store.ensureInterpreterExists(handle.uri); + _ = try interpreter.document_store.ensureInterpreterExists(handle.uri); - return InterpretResult{ .value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .type = {} }), - .value_data = try interpreter.createValueData(.{ .type = handle.interpreter.?.root_type.? }), - } }; + return InterpretResult{ + .value = Value{ + .interpreter = interpreter, + .node_idx = node_idx, + .ty = Index.type_type, + .val = .none, // TODO + }, + }; } if (std.mem.eql(u8, call_name, "@TypeOf")) { if (params.len != 1) return error.InvalidBuiltin; - const value = try (try interpreter.interpret(params[0], scope, options)).getValue(); + const value = try (try interpreter.interpret(params[0], namespace, options)).getValue(); return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .type = {} }), - .value_data = try interpreter.createValueData(.{ .type = value.type }), + .ty = Index.type_type, + .val = value.ty, } }; } if (std.mem.eql(u8, call_name, "@hasDecl")) { if (params.len != 2) return error.InvalidBuiltin; - const value = try (try interpreter.interpret(params[0], scope, options)).getValue(); - const field_name = try (try interpreter.interpret(params[1], scope, options)).getValue(); + const value = try (try interpreter.interpret(params[0], namespace, options)).getValue(); + const field_name = try (try interpreter.interpret(params[1], namespace, options)).getValue(); - if (value.type.getTypeInfo() != .type) return error.InvalidBuiltin; - if (field_name.type.getTypeInfo() != .pointer) return error.InvalidBuiltin; // Check if it's a []const u8 + 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 ti = value.value_data.type.getTypeInfo(); - if (ti.getScopeOfType() == null) return error.InvalidBuiltin; + const value_namespace = interpreter.ip.indexToKey(value.val).getNamespace(interpreter.ip); + if (value_namespace == .none) return error.InvalidBuiltin; + + const name = interpreter.ip.indexToKey(field_name.val).bytes; // TODO add checks + + const decls = interpreter.namespaces.items(.decls)[@enumToInt(value_namespace)]; + const has_decl = decls.contains(name); return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ .bool = {} }), - .value_data = try interpreter.createValueData(.{ .bool = ti.getScopeOfType().?.declarations.contains(field_name.value_data.slice_of_const_u8) }), + .ty = Index.bool_type, + .val = if (has_decl) Index.bool_true else Index.bool_false, } }; } if (std.mem.eql(u8, call_name, "@as")) { if (params.len != 2) return error.InvalidBuiltin; - const as_type = try (try interpreter.interpret(params[0], scope, options)).getValue(); - const value = try (try interpreter.interpret(params[1], scope, options)).getValue(); + const as_type = try (try interpreter.interpret(params[0], namespace, options)).getValue(); + const value = try (try interpreter.interpret(params[1], namespace, options)).getValue(); - if (as_type.type.getTypeInfo() != .type) return error.InvalidBuiltin; + if (as_type.ty != Index.type_type) return error.InvalidBuiltin; - return InterpretResult{ .value = try interpreter.cast(node_idx, as_type.value_data.type, value) }; + return InterpretResult{ + .value = Value{ + .interpreter = interpreter, + .node_idx = node_idx, + .ty = as_type.val, + .val = value.val, // TODO port Sema.coerceExtra to InternPool + }, + }; } log.err("Builtin not implemented: {s}", .{call_name}); return error.InvalidBuiltin; }, .string_literal => { - const value = tree.getNodeSource(node_idx)[1 .. tree.getNodeSource(node_idx).len - 1]; - var val = Value{ + 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, + } }); + + return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - // TODO: This is literally the wrong type lmao - // the actual type is *[len:0]u8 because we're pointing - // to a fixed size value in the data(?) section (when we're compilign zig code) - .type = try interpreter.createType(node_idx, .{ - .pointer = .{ - .size = .one, - .is_const = true, - .is_volatile = false, - .child = try interpreter.createType(0, .{ .int = .{ - .bits = 8, - .signedness = .unsigned, - } }), - .is_allowzero = false, - - .sentinel = null, - }, - }), - .value_data = try interpreter.createValueData(.{ .slice_of_const_u8 = value }), - }; - - // TODO: Add type casting, sentinel - // TODO: Should this be a `*const [len:0]u8`? - // try val.value_data.slice_ptr.append(interpreter.allocator, .{ .unsigned_int = 0 }); - - return InterpretResult{ .value = val }; + .ty = string_literal_type, + .val = try interpreter.ip.get(interpreter.allocator, Key{ .bytes = str }), + } }; }, // TODO: Add comptime autodetection; e.g. const MyArrayList = std.ArrayList(u8) .@"comptime" => { - return try interpreter.interpret(data[node_idx].lhs, scope, .{}); + return try interpreter.interpret(data[node_idx].lhs, namespace, .{}); }, - // .fn_proto, - // .fn_proto_multi, - // .fn_proto_one, - // .fn_proto_simple, - .fn_decl => { - // var buf: [1]Ast.Node.Index = undefined; - // const func = tree.fullFnProto(node_idx, &buf).?; + .fn_proto, + .fn_proto_multi, + .fn_proto_one, + .fn_proto_simple, + .fn_decl, + => { + var buf: [1]Ast.Node.Index = undefined; + const func = tree.fullFnProto(&buf, node_idx).?; - // TODO: Add params + if (func.name_token == null) return InterpretResult{ .nothing = {} }; + const name = offsets.tokenToSlice(tree, func.name_token.?); - var type_info = TypeInfo{ - .@"fn" = .{ - .return_type = null, - }, - }; + // TODO: Resolve function type + + const function_type = try interpreter.ip.get(interpreter.allocator, Key{ .function_type = .{ + .calling_convention = .Unspecified, + .alignment = 0, + .is_generic = false, + .is_var_args = false, + .return_type = Index.none, + .args = &.{}, + } }); // var it = func.iterate(&tree); // while (ast.nextFnParam(&it)) |param| { @@ -1266,20 +983,21 @@ pub fn interpret( // if ((try interpreter.interpret(func.ast.return_type, func_scope_idx, .{ .observe_values = true, .is_comptime = true })).maybeGetValue()) |value| // fnd.return_type = value.value_data.@"type"; - var value = Value{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = try interpreter.createType(node_idx, type_info), - .value_data = try interpreter.createValueData(.{ .@"fn" = {} }), - }; + if (namespace != .none) { + const decls = &interpreter.namespaces.items(.decls)[@enumToInt(namespace)]; - const name = analysis.getDeclName(tree, node_idx).?; - try scope.?.declarations.put(interpreter.allocator, name, .{ - .scope = scope.?, - .node_idx = node_idx, - .name = name, - .value = value, - }); + const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ + .name = name, + .node_idx = node_idx, + .ty = Index.type_type, + .val = function_type, + .alignment = 0, // TODO + .address_space = .generic, // TODO + .is_pub = false, // TODO + .is_exported = false, // TODO + }); + try decls.putNoClobber(interpreter.allocator, name, decl_index); + } return InterpretResult{ .nothing = {} }; }, @@ -1299,14 +1017,13 @@ pub fn interpret( defer args.deinit(interpreter.allocator); for (call_full.ast.params) |param| { - try args.append(interpreter.allocator, try (try interpreter.interpret(param, scope, .{})).getValue()); + args.appendAssumeCapacity(try (try interpreter.interpret(param, namespace, .{})).getValue()); } - const func_id_result = try interpreter.interpret(call_full.ast.fn_expr, interpreter.root_type.?.getTypeInfo().getScopeOfType().?, .{}); + const func_id_result = try interpreter.interpret(call_full.ast.fn_expr, namespace, .{}); const func_id_val = try func_id_result.getValue(); - const call_res = try interpreter.call(interpreter.root_type.?.getTypeInfo().getScopeOfType().?, func_id_val.node_idx, args.items, options); - // defer call_res.scope.deinit(); + const call_res = try interpreter.call(namespace, func_id_val.node_idx, args.items, options); // TODO: Figure out call result memory model; this is actually fine because newScope // makes this a child of the decl scope which is freed on refresh... in theory @@ -1316,65 +1033,69 @@ pub fn interpret( }; }, .bool_not => { - const result = try interpreter.interpret(data[node_idx].lhs, scope, .{}); - const value = (try result.getValue()); - if (value.value_data.* != .bool) return error.InvalidOperation; - return InterpretResult{ - .value = .{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = value.type, - .value_data = try interpreter.createValueData(.{ .bool = !value.value_data.bool }), - }, - }; + const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); + const value = try result.getValue(); + + if (value.ty != Index.bool_type) { + try interpreter.recordError( + node_idx, + "invalid_deref", + "expected type `bool` but got `{}`", + .{value.ty.fmtType(interpreter.ip)}, + ); + return error.InvalidOperation; + } + + std.debug.assert(value.val == Index.bool_false or value.val == Index.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, + } }; }, .address_of => { // TODO: Make const pointers if we're drawing from a const; // variables are the only non-const(?) - const result = try interpreter.interpret(data[node_idx].lhs, scope, .{}); + const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); const value = (try result.getValue()); - return InterpretResult{ - .value = .{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = try interpreter.createType(node_idx, .{ - .pointer = .{ - .size = .one, - .is_const = false, - .is_volatile = false, - .child = value.type, - .is_allowzero = false, + const pointer_type = try interpreter.ip.get(interpreter.allocator, Key{ .pointer_type = .{ + .elem_type = value.ty, + .sentinel = .none, + .alignment = 0, + .size = .One, + .is_const = false, + .is_volatile = false, + .is_allowzero = false, + .address_space = .generic, + } }); - .sentinel = null, - }, - }), - .value_data = try interpreter.createValueData(.{ .one_ptr = value.value_data }), - }, - }; + return InterpretResult{ .value = .{ + .interpreter = interpreter, + .node_idx = node_idx, + .ty = pointer_type, + .val = value.val, + } }; }, .deref => { - const result = try interpreter.interpret(data[node_idx].lhs, scope, .{}); + const result = try interpreter.interpret(data[node_idx].lhs, namespace, .{}); const value = (try result.getValue()); - const ti = value.type.getTypeInfo(); + const type_key = interpreter.ip.indexToKey(value.ty); - if (ti != .pointer) { - try interpreter.recordError(node_idx, "invalid_deref", try std.fmt.allocPrint(interpreter.allocator, "cannot deference non-pointer", .{})); + if (type_key != .pointer_type) { + try interpreter.recordError(node_idx, "invalid_deref", "cannot deference non-pointer", .{}); return error.InvalidOperation; } - // TODO: Check if this is a one_ptr or not - - return InterpretResult{ - .value = .{ - .interpreter = interpreter, - .node_idx = node_idx, - .type = ti.pointer.child, - .value_data = value.value_data.one_ptr, - }, - }; + return InterpretResult{ .value = .{ + .interpreter = interpreter, + .node_idx = node_idx, + .ty = type_key.pointer_type.elem_type, + .val = value.val, + } }; }, else => { log.err("Unhandled {any}", .{tags[node_idx]}); @@ -1384,7 +1105,7 @@ pub fn interpret( } pub const CallResult = struct { - scope: *InterpreterScope, + namespace: Namespace.Index, result: union(enum) { value: Value, nothing, @@ -1393,7 +1114,7 @@ pub const CallResult = struct { pub fn call( interpreter: *ComptimeInterpreter, - scope: ?*InterpreterScope, + namespace: Namespace.Index, func_node_idx: Ast.Node.Index, arguments: []const Value, options: InterpretOptions, @@ -1403,47 +1124,60 @@ pub fn call( // TODO: type check args const tree = interpreter.getHandle().tree; - const tags = tree.nodes.items(.tag); + const node_tags = tree.nodes.items(.tag); - if (tags[func_node_idx] != .fn_decl) return error.CriticalAstFailure; - - // TODO: Make argument scope to evaluate arguments in - var fn_scope = try interpreter.newScope(scope, func_node_idx); + if (node_tags[func_node_idx] != .fn_decl) return error.CriticalAstFailure; var buf: [1]Ast.Node.Index = undefined; - var proto = tree.fullFnProto(&buf, func_node_idx).?; + var proto = tree.fullFnProto(&buf, func_node_idx) orelse return error.CriticalAstFailure; + + // TODO: Make argument namespace to evaluate arguments in + try interpreter.namespaces.append(interpreter.allocator, .{ + .parent = namespace, + .node_idx = func_node_idx, + .ty = .none, + }); + const fn_namespace = @intToEnum(Namespace.Index, interpreter.namespaces.len - 1); var arg_it = proto.iterate(&tree); var arg_index: usize = 0; while (ast.nextFnParam(&arg_it)) |param| { if (arg_index >= arguments.len) return error.MissingArguments; - var tex = try (try interpreter.interpret(param.type_expr, fn_scope, options)).getValue(); - if (tex.type.getTypeInfo() != .type) { + var tex = try (try interpreter.interpret(param.type_expr, fn_namespace, options)).getValue(); + if (tex.ty != Index.type_type) { try interpreter.recordError( param.type_expr, "expected_type", - std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(tex.type.getTypeInfo())}) catch return error.CriticalAstFailure, + "expected type 'type', found '{}'", + .{tex.ty.fmtType(interpreter.ip)}, ); return error.InvalidCast; } - if (param.name_token) |nt| { - const decl = Declaration{ - .scope = fn_scope, - .node_idx = param.type_expr, - .name = tree.tokenSlice(nt), - .value = try interpreter.cast(arguments[arg_index].node_idx, tex.value_data.type, arguments[arg_index]), - }; - try fn_scope.declarations.put(interpreter.allocator, tree.tokenSlice(nt), decl); + if (param.name_token) |name_token| { + const name = offsets.tokenToSlice(tree, name_token); + + const decls = &interpreter.namespaces.items(.decls)[@enumToInt(fn_namespace)]; + const decl_index = try interpreter.ip.createDecl(interpreter.allocator, .{ + .name = name, + .node_idx = name_token, + .ty = tex.val, + .val = arguments[arg_index].val, + .alignment = 0, // TODO + .address_space = .generic, // TODO + .is_pub = true, // TODO + .is_exported = false, // TODO + }); + try decls.putNoClobber(interpreter.allocator, name, decl_index); arg_index += 1; } } const body = tree.nodes.items(.data)[func_node_idx].rhs; - const result = try interpreter.interpret(body, fn_scope, .{}); + const result = try interpreter.interpret(body, fn_namespace, .{}); // TODO: Defers return CallResult{ - .scope = fn_scope, + .namespace = fn_namespace, .result = switch (result) { .@"return", .nothing => .{ .nothing = {} }, // nothing could be due to an error .return_with_value => |v| .{ .value = v }, diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index 99c25db..4863860 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -68,6 +68,10 @@ pub const Handle = struct { associated_build_file: ?Uri = null, pub fn deinit(self: *Handle, allocator: std.mem.Allocator) void { + if (self.interpreter) |interpreter| { + interpreter.deinit(); + allocator.destroy(interpreter); + } self.document_scope.deinit(allocator); self.tree.deinit(allocator); allocator.free(self.text); @@ -1014,16 +1018,26 @@ pub fn enumCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle return try self.tagStoreCompletionItems(arena, handle, "enum_completions"); } -pub fn ensureInterpreterExists(self: *DocumentStore, uri: Uri) !void { +pub fn ensureInterpreterExists(self: *DocumentStore, uri: Uri) !*ComptimeInterpreter { var handle = self.handles.get(uri).?; - if (handle.interpreter == null) { - var int = try self.allocator.create(ComptimeInterpreter); - int.* = ComptimeInterpreter{ + if (handle.interpreter != null) return handle.interpreter.?; + + { + var interpreter = try self.allocator.create(ComptimeInterpreter); + errdefer self.allocator.destroy(interpreter); + + var ip = try ComptimeInterpreter.InternPool.init(self.allocator); + errdefer ip.deinit(self.allocator); + + interpreter.* = ComptimeInterpreter{ .allocator = self.allocator, + .ip = ip, .document_store = self, .uri = uri, }; - handle.interpreter = int; - _ = try int.interpret(0, null, .{}); + handle.interpreter = interpreter; } + + _ = try handle.interpreter.?.interpret(0, .none, .{}); + return handle.interpreter.?; } diff --git a/src/Server.zig b/src/Server.zig index 6c47d09..caa47b6 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -21,6 +21,7 @@ const tracy = @import("tracy.zig"); const uri_utils = @import("uri.zig"); const diff = @import("diff.zig"); const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); +const analyser = @import("analyser/analyser.zig"); const data = @import("data/data.zig"); const snipped_data = @import("data/snippets.zig"); @@ -362,7 +363,7 @@ fn generateDiagnostics(server: *Server, handle: DocumentStore.Handle) error{OutO var err_it = int.errors.iterator(); while (err_it.next()) |err| { - try diagnostics.append(allocator, .{ + diagnostics.appendAssumeCapacity(.{ .range = offsets.nodeToRange(tree, err.key_ptr.*, server.offset_encoding), .severity = .Error, .code = .{ .string = err.value_ptr.code }, @@ -597,30 +598,10 @@ fn typeToCompletion( ), .primitive, .array_index => {}, .@"comptime" => |co| { - const ti = co.type.getTypeInfo(); - switch (ti) { - .@"struct" => |st| { - var fit = st.fields.iterator(); - while (fit.next()) |entry| { - try list.append(allocator, .{ - .label = entry.key_ptr.*, - .kind = .Field, - .insertText = entry.key_ptr.*, - .insertTextFormat = .PlainText, - }); - } - - var it = st.scope.declarations.iterator(); - while (it.next()) |entry| { - try list.append(allocator, .{ - .label = entry.key_ptr.*, - .kind = if (entry.value_ptr.isConstant()) .Constant else .Variable, - .insertText = entry.key_ptr.*, - .insertTextFormat = .PlainText, - }); - } - }, - else => {}, + 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); } }, } @@ -939,7 +920,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.interpreter.formatTypeInfo(co.type.getTypeInfo())}), + .@"comptime" => |co| try std.fmt.allocPrint(server.arena.allocator(), "{}", .{co.value.ty.fmtType(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 new file mode 100644 index 0000000..25479ee --- /dev/null +++ b/src/analyser/InternPool.zig @@ -0,0 +1,3601 @@ +/// Based on src/InternPool.zig from the zig codebase +/// https://github.com/ziglang/zig/blob/master/src/InternPool.zig +map: std.AutoArrayHashMapUnmanaged(void, void) = .{}, +items: std.MultiArrayList(Item) = .{}, +extra: std.ArrayListUnmanaged(u8) = .{}, + +decls: std.SegmentedList(InternPool.Decl, 0) = .{}, +structs: std.SegmentedList(InternPool.Struct, 0) = .{}, +enums: std.SegmentedList(InternPool.Enum, 0) = .{}, +unions: std.SegmentedList(InternPool.Union, 0) = .{}, + +const InternPool = @This(); +const std = @import("std"); +const builtin = @import("builtin"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const expect = std.testing.expect; + +const encoding = @import("encoding.zig"); + +pub const Int = packed struct { + signedness: std.builtin.Signedness, + bits: u16, +}; + +pub const Pointer = packed struct { + elem_type: Index, + sentinel: Index = .none, + alignment: u16 = 0, + size: std.builtin.Type.Pointer.Size, + bit_offset: u16 = 0, + host_size: u16 = 0, + is_const: bool = false, + is_volatile: bool = false, + is_allowzero: bool = false, + address_space: std.builtin.AddressSpace = .generic, +}; + +pub const Array = packed struct { + len: u64, + child: Index, + sentinel: Index = .none, +}; + +pub const FieldStatus = enum { + none, + field_types_wip, + have_field_types, + layout_wip, + have_layout, + fully_resolved_wip, + fully_resolved, +}; + +pub const StructIndex = enum(u32) { _ }; + +pub const Struct = struct { + fields: std.StringArrayHashMapUnmanaged(Field), + namespace: NamespaceIndex, + layout: std.builtin.Type.ContainerLayout = .Auto, + backing_int_ty: Index, + status: FieldStatus, + + pub const Field = packed struct { + ty: Index, + default_value: Index = .none, + alignment: u16 = 0, + is_comptime: bool = false, + }; +}; + +pub const Optional = packed struct { + payload_type: Index, +}; + +pub const ErrorUnion = packed struct { + error_set_type: Index, + payload_type: Index, +}; + +pub const ErrorSet = struct { + /// every element is guaranteed to be .bytes + names: []const Index, +}; + +pub const EnumIndex = enum(u32) { _ }; + +pub const Enum = struct { + tag_type: Index, + fields: std.StringArrayHashMapUnmanaged(void), + values: std.AutoArrayHashMapUnmanaged(Index, void), + namespace: NamespaceIndex, + tag_type_infered: bool, +}; + +pub const Function = struct { + args: []const Index, + /// zig only lets the first 32 arguments be `comptime` + args_is_comptime: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), + /// zig only lets the first 32 arguments be generic + args_is_generic: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), + /// zig only lets the first 32 arguments be `noalias` + args_is_noalias: std.StaticBitSet(32) = std.StaticBitSet(32).initEmpty(), + return_type: Index, + alignment: u16 = 0, + calling_convention: std.builtin.CallingConvention = .Unspecified, + is_generic: bool = false, + is_var_args: bool = false, +}; + +pub const UnionIndex = enum(u32) { _ }; + +pub const Union = struct { + tag_type: Index, + fields: std.StringArrayHashMapUnmanaged(Field), + namespace: NamespaceIndex, + layout: std.builtin.Type.ContainerLayout = .Auto, + status: FieldStatus, + + pub const Field = packed struct { + ty: Index, + alignment: u16, + }; +}; + +pub const Tuple = struct { + types: []const Index, + /// Index.none elements are used to indicate runtime-known. + values: []const Index, +}; + +pub const Vector = packed struct { + len: u32, + child: Index, +}; + +pub const AnyFrame = packed struct { + child: Index, +}; + +pub const BigInt = std.math.big.int.Const; + +pub const Bytes = []const u8; + +pub const Aggregate = []const Index; + +pub const UnionValue = packed struct { + field_index: u32, + val: Index, +}; + +pub const DeclIndex = enum(u32) { _ }; + +pub const Decl = struct { + name: []const u8, + node_idx: u32, + ty: Index, + val: Index, + alignment: u16, + address_space: std.builtin.AddressSpace, + is_pub: bool, + is_exported: bool, +}; + +pub const Key = union(enum) { + simple_type: SimpleType, + simple_value: SimpleValue, + + int_type: Int, + pointer_type: Pointer, + array_type: Array, + /// TODO consider *Struct instead of StructIndex + struct_type: StructIndex, + optional_type: Optional, + error_union_type: ErrorUnion, + error_set_type: ErrorSet, + /// TODO consider *Enum instead of EnumIndex + enum_type: EnumIndex, + function_type: Function, + /// TODO consider *Union instead of UnionIndex + union_type: UnionIndex, + tuple_type: Tuple, + vector_type: Vector, + anyframe_type: AnyFrame, + + int_u64_value: u64, + int_i64_value: i64, + int_big_value: BigInt, + float_16_value: f16, + float_32_value: f32, + float_64_value: f64, + float_80_value: f80, + float_128_value: f128, + + bytes: Bytes, + aggregate: Aggregate, + union_value: UnionValue, + + // slice + // error + // error union + + pub fn eql(a: Key, b: Key) bool { + return deepEql(a, b); + } + + pub fn hash(a: Key) u32 { + var hasher = std.hash.Wyhash.init(0); + deepHash(&hasher, a); + return @truncate(u32, hasher.final()); + } + + pub fn tag(key: Key) Tag { + return switch (key) { + .simple_type => .simple_type, + .simple_value => .simple_value, + + .int_type => |int_info| switch (int_info.signedness) { + .signed => .type_int_signed, + .unsigned => .type_int_unsigned, + }, + .pointer_type => .type_pointer, + .array_type => .type_array, + .struct_type => .type_struct, + .optional_type => .type_optional, + .error_union_type => .type_error_union, + .error_set_type => .type_error_set, + .enum_type => .type_enum, + .function_type => .type_function, + .union_type => .type_union, + .tuple_type => .type_tuple, + .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, + .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, + + .bytes => .bytes, + .aggregate => .aggregate, + .union_value => .union_value, + }; + } + + pub fn zigTypeTag(key: Key) std.builtin.TypeId { + return switch (key) { + .simple_type => |simple| switch (simple) { + .f16, + .f32, + .f64, + .f80, + .f128, + .c_longdouble, + => .Float, + + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + => .Int, + + .comptime_int => .ComptimeInt, + .comptime_float => .ComptimeFloat, + + .anyopaque => .Opaque, + .bool => .Bool, + .void => .Void, + .type => .Type, + .anyerror => .ErrorSet, + .noreturn => .NoReturn, + .anyframe_type => .AnyFrame, + .null_type => .Null, + .undefined_type => .Undefined, + .enum_literal_type => .EnumLiteral, + + .atomic_order => .Enum, + .atomic_rmw_op => .Enum, + .calling_convention => .Enum, + .address_space => .Enum, + .float_mode => .Enum, + .reduce_op => .Enum, + .modifier => .Enum, + .prefetch_options => .Struct, + .export_options => .Struct, + .extern_options => .Struct, + .type_info => .Union, + + .generic_poison => unreachable, + }, + + .int_type => .Int, + .pointer_type => .Pointer, + .array_type => .Array, + .struct_type => .Struct, + .optional_type => .Optional, + .error_union_type => .ErrorUnion, + .error_set_type => .ErrorSet, + .enum_type => .Enum, + .function_type => .Fn, + .union_type => .Union, + .tuple_type => .Struct, + .vector_type => .Vector, + .anyframe_type => .AnyFrame, + + .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, + }; + } + + pub fn isType(key: Key) bool { + return switch (key) { + .simple_type, + .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, + => true, + + .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, + + .bytes, + .aggregate, + .union_value, + => false, + }; + } + + 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 { + var key: Key = ty; + + while (true) switch (key) { + .simple_type => |simple| switch (simple) { + .usize => return .{ .signedness = .signed, .bits = target.cpu.arch.ptrBitWidth() }, + .isize => return .{ .signedness = .unsigned, .bits = target.cpu.arch.ptrBitWidth() }, + + .c_short => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.short) }, + .c_ushort => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ushort) }, + .c_int => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.int) }, + .c_uint => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.uint) }, + .c_long => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.long) }, + .c_ulong => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ulong) }, + .c_longlong => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.longlong) }, + .c_ulonglong => return .{ .signedness = .unsigned, .bits = target.c_type_bit_size(.ulonglong) }, + .c_longdouble => return .{ .signedness = .signed, .bits = target.c_type_bit_size(.longdouble) }, + + // TODO revisit this when error sets support custom int types (comment taken from zig codebase) + .anyerror => return .{ .signedness = .unsigned, .bits = 16 }, + + else => unreachable, + }, + .int_type => |int_info| return int_info, + .enum_type => |enum_index| { + const enum_info = ip.getEnum(enum_index); + key = ip.indexToKey(enum_info.tag_type); + }, + .struct_type => |struct_index| { + const struct_info = ip.getStruct(struct_index); + assert(struct_info.layout == .Packed); + key = ip.indexToKey(struct_info.backing_int_ty); + }, + // TODO revisit this when error sets support custom int types (comment taken from zig codebase) + .error_set_type => return .{ .signedness = .unsigned, .bits = 16 }, + .vector_type => |vector_info| { + assert(vector_info.len == 1); + key = ip.indexToKey(vector_info.child); + }, + else => unreachable, + }; + } + + /// Asserts the type is a fixed-size float or comptime_float. + /// Returns 128 for comptime_float types. + pub fn floatBits(ty: Key, target: std.Target) u16 { + return switch (ty.simple_type) { + .f16 => 16, + .f32 => 32, + .f64 => 64, + .f80 => 80, + .f128, .comptime_float => 128, + .c_longdouble => target.c_type_bit_size(.longdouble), + + else => unreachable, + }; + } + + pub fn isConstPtr(ty: Key) bool { + return switch (ty) { + .pointer_type => |pointer_info| pointer_info.is_const, + else => false, + }; + } + + /// For pointer-like optionals, returns true, otherwise returns the allowzero property + /// of pointers. + pub fn ptrAllowsZero(ty: Key, ip: *const InternPool) bool { + if (ty.pointer_type.is_allowzero) return true; + return ty.isPtrLikeOptional(ip); + } + + /// Returns true if the type is optional and would be lowered to a single pointer + /// address value, using 0 for null. Note that this returns true for C pointers. + pub fn isPtrLikeOptional(ty: Key, ip: *const InternPool) bool { + switch (ty) { + .optional_type => |optional_info| { + const child_ty = optional_info.payload_type; + const child_key = ip.indexToKey(child_ty); + if (child_key != .pointer_type) return false; + const info = child_key.pointer_type; + switch (info.size) { + .Slice, .C => return false, + .Many, .One => return !info.is_allowzero, + } + }, + .pointer_type => |pointer_info| return pointer_info.size == .C, + else => return false, + } + } + + pub fn elemType2(ty: Key) Index { + return switch (ty) { + .simple_type => |simple| switch (simple) { + .anyframe_type => Index.void_type, + else => unreachable, + }, + .pointer_type => |pointer_info| pointer_info.elem_type, + .array_type => |array_info| array_info.child, + .optional_type => |optional_info| optional_info.payload_type, + .vector_type => |vector_info| vector_info.child, + .anyframe_type => |anyframe_info| anyframe_info.child, + else => unreachable, + }; + } + + /// Asserts the type is an array, pointer or vector. + pub fn sentinel(ty: Key) Index { + return switch (ty) { + .pointer_type => |pointer_info| pointer_info.sentinel, + .array_type => |array_info| array_info.sentinel, + .vector_type => Index.none, + else => unreachable, + }; + } + + pub fn getNamespace(ty: Key, ip: InternPool) NamespaceIndex { + return switch (ty) { + .struct_type => |struct_index| ip.getStruct(struct_index).namespace, + .enum_type => |enum_index| ip.getEnum(enum_index).namespace, + .union_type => |union_index| ip.getUnion(union_index).namespace, + else => .none, + }; + } + + pub fn onePossibleValue(ty: Key, ip: InternPool) Index { + return switch (ty) { + .simple_type => |simple| switch (simple) { + .f16, + .f32, + .f64, + .f80, + .f128, + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .anyopaque, + .bool, + .type, + .anyerror, + .comptime_int, + .comptime_float, + .anyframe_type, + .enum_literal_type, + => Index.none, + + .void => Index.void_value, + .noreturn => Index.unreachable_value, + .null_type => Index.null_value, + .undefined_type => Index.undefined_value, + + .atomic_order, + .atomic_rmw_op, + .calling_convention, + .address_space, + .float_mode, + .reduce_op, + .modifier, + .prefetch_options, + .export_options, + .extern_options, + .type_info, + => Index.none, + + .generic_poison => unreachable, + }, + .int_type => |int_info| { + if (int_info.bits == 0) { + switch (int_info.signedness) { + .unsigned => return Index.zero, + .signed => return Index.zero, // do we need a signed zero? + } + } + return Index.none; + }, + .pointer_type => Index.none, + .array_type => |array_info| { + if (array_info.len == 0) { + return Index.empty_aggregate; + } else if (ip.indexToKey(array_info.child).onePossibleValue(ip) != Index.none) { + return Index.the_only_possible_value; + } + return Index.none; + }, + .struct_type => |struct_index| { + const struct_info = ip.getStruct(struct_index); + var field_it = struct_info.fields.iterator(); + while (field_it.next()) |entry| { + if (entry.value_ptr.is_comptime) continue; + if (ip.indexToKey(entry.value_ptr.ty).onePossibleValue(ip) != Index.none) continue; + return Index.none; + } + return Index.empty_aggregate; + }, + .optional_type => |optional_info| { + if (optional_info.payload_type == Index.noreturn_type) { + return Index.null_value; + } + return Index.none; + }, + .error_union_type => Index.none, + .error_set_type => Index.none, + .enum_type => |enum_index| { + const enum_info = ip.getEnum(enum_index); + return switch (enum_info.fields.count()) { + 0 => Index.unreachable_value, + 1 => enum_info.values.keys()[0], + else => Index.none, + }; + }, + .function_type => Index.none, + .union_type => panicOrElse("TODO", Index.none), + .tuple_type => panicOrElse("TODO", Index.none), + .vector_type => |vector_info| { + if (vector_info.len == 0) { + return panicOrElse("TODO return empty array value", Index.none); + } + return ip.indexToKey(vector_info.child).onePossibleValue(ip); + }, + .anyframe_type => Index.none, + + .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, + }; + } + + 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, + options: FormatOptions = .{}, + ip: InternPool, + }; + + pub const FormatOptions = struct {}; + + fn formatType( + ctx: TypeFormatContext, + comptime fmt: []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); + } + + 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); + } + } + + fn printTypeInternal(ty: Key, ip: InternPool, writer: anytype) @TypeOf(writer).Error!?Index { + switch (ty) { + .simple_type => |simple| switch (simple) { + .f16, + .f32, + .f64, + .f80, + .f128, + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .anyopaque, + .bool, + .void, + .type, + .anyerror, + .comptime_int, + .comptime_float, + .noreturn, + .anyframe_type, + => try writer.writeAll(@tagName(simple)), + + .null_type => try writer.writeAll("@TypeOf(null)"), + .undefined_type => try writer.writeAll("@TypeOf(undefined)"), + .enum_literal_type => try writer.writeAll("@TypeOf(.enum_literal)"), + + .atomic_order => try writer.writeAll("std.builtin.AtomicOrder"), + .atomic_rmw_op => try writer.writeAll("std.builtin.AtomicRmwOp"), + .calling_convention => try writer.writeAll("std.builtin.CallingConvention"), + .address_space => try writer.writeAll("std.builtin.AddressSpace"), + .float_mode => try writer.writeAll("std.builtin.FloatMode"), + .reduce_op => try writer.writeAll("std.builtin.ReduceOp"), + .modifier => try writer.writeAll("std.builtin.CallModifier"), + .prefetch_options => try writer.writeAll("std.builtin.PrefetchOptions"), + .export_options => try writer.writeAll("std.builtin.ExportOptions"), + .extern_options => try writer.writeAll("std.builtin.ExternOptions"), + .type_info => try writer.writeAll("std.builtin.Type"), + .generic_poison => unreachable, + }, + .int_type => |int_info| switch (int_info.signedness) { + .signed => try writer.print("i{}", .{int_info.bits}), + .unsigned => try writer.print("u{}", .{int_info.bits}), + }, + .pointer_type => |pointer_info| { + 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)}), + } + } else switch (pointer_info.size) { + .One => try writer.writeAll("*"), + .Many => try writer.writeAll("[*]"), + .C => try writer.writeAll("[*c]"), + .Slice => try writer.writeAll("[]"), + } + + if (pointer_info.alignment != 0) { + try writer.print("align({d}", .{pointer_info.alignment}); + + if (pointer_info.bit_offset != 0 or pointer_info.host_size != 0) { + try writer.print(":{d}:{d}", .{ pointer_info.bit_offset, pointer_info.host_size }); + } + + try writer.writeAll(") "); + } + + if (pointer_info.address_space != .generic) { + try writer.print("addrspace(.{s}) ", .{@tagName(pointer_info.address_space)}); + } + + if (pointer_info.is_const) try writer.writeAll("const "); + if (pointer_info.is_volatile) try writer.writeAll("volatile "); + if (pointer_info.is_allowzero and pointer_info.size != .C) try writer.writeAll("allowzero "); + + return pointer_info.elem_type; + }, + .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.writeByte(']'); + + return array_info.child; + }, + .struct_type => return panicOrElse("TODO", null), + .optional_type => |optional_info| { + try writer.writeByte('?'); + return optional_info.payload_type; + }, + .error_union_type => |error_union_info| { + try printType(error_union_info.error_set_type, ip, writer); + try writer.writeByte('!'); + return error_union_info.payload_type; + }, + .error_set_type => |error_set_info| { + const names = error_set_info.names; + try writer.writeAll("error{"); + for (names) |name, i| { + if (i != 0) try writer.writeByte(','); + try writer.writeAll(ip.indexToKey(name).bytes); + } + try writer.writeByte('}'); + }, + .enum_type => return panicOrElse("TODO", null), + .function_type => |function_info| { + try writer.writeAll("fn("); + + for (function_info.args) |arg_ty, i| { + if (i != 0) try writer.writeAll(", "); + + if (i < 32) { + if (function_info.args_is_comptime.isSet(i)) { + try writer.writeAll("comptime "); + } + if (function_info.args_is_noalias.isSet(i)) { + try writer.writeAll("noalias "); + } + } + + try printType(arg_ty, ip, writer); + } + + if (function_info.is_var_args) { + if (function_info.args.len != 0) { + try writer.writeAll(", "); + } + try writer.writeAll("..."); + } + try writer.writeAll(") "); + + if (function_info.alignment != 0) { + try writer.print("align({d}) ", .{function_info.alignment}); + } + if (function_info.calling_convention != .Unspecified) { + try writer.print("callconv(.{s}) ", .{@tagName(function_info.calling_convention)}); + } + + return function_info.return_type; + }, + .union_type => return panicOrElse("TODO", null), + .tuple_type => |tuple_info| { + try writer.writeAll("tuple{"); + for (tuple_info.types) |field_ty, i| { + if (i != 0) try writer.writeAll(", "); + const val = tuple_info.values[i]; + if (val != Index.none) { + try writer.writeAll("comptime "); + } + try printType(field_ty, ip, writer); + if (val != Index.none) { + try writer.print(" = {}", .{val.fmtValue(field_ty, ip)}); + } + } + try writer.writeByte('}'); + }, + .vector_type => |vector_info| { + try writer.print("@Vector({d},{})", .{ + vector_info.len, + vector_info.child.fmtType(ip), + }); + }, + .anyframe_type => |anyframe_info| { + try writer.writeAll("anyframe->"); + 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("{}"), + .unreachable_value => try writer.writeAll("unreachable"), + .null_value => try writer.writeAll("null"), + .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, + }, + + .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), + .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)}), + + .bytes => |bytes| try writer.print("\"{}\"", .{std.zig.fmtEscapes(bytes)}), + .aggregate => |aggregate| { + const struct_info = ip.getStruct(ip.indexToKey(ty).struct_type); + assert(aggregate.len == struct_info.fields.count()); + + try writer.writeAll(".{"); + var i: u32 = 0; + while (i < aggregate.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.writeByte('}'); + }, + .union_value => |union_value| { + const union_info = ip.getUnion(ip.indexToKey(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), + }); + }, + } + } + + pub fn fmtType(ty: Key, ip: InternPool) std.fmt.Formatter(formatType) { + 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, + .ip = ip, + } }; + } +}; + +pub const Item = struct { + tag: Tag, + /// The doc comments on the respective Tag explain how to interpret this. + data: u32, +}; + +/// Represents an index into `map`. It represents the canonical index +/// of a `Value` within this `InternPool`. The values are typed. +/// Two values which have the same type can be equality compared simply +/// by checking if their indexes are equal, provided they are both in +/// the same `InternPool`. +pub const Index = enum(u32) { + u1_type, + u8_type, + i8_type, + u16_type, + i16_type, + u29_type, + u32_type, + i32_type, + u64_type, + i64_type, + u128_type, + i128_type, + usize_type, + isize_type, + c_short_type, + c_ushort_type, + c_int_type, + c_uint_type, + c_long_type, + c_ulong_type, + c_longlong_type, + c_ulonglong_type, + c_longdouble_type, + f16_type, + f32_type, + f64_type, + f80_type, + f128_type, + anyopaque_type, + bool_type, + void_type, + type_type, + anyerror_type, + comptime_int_type, + comptime_float_type, + noreturn_type, + anyframe_type, + null_type, + undefined_type, + enum_literal_type, + atomic_order_type, + atomic_rmw_op_type, + calling_convention_type, + address_space_type, + float_mode_type, + reduce_op_type, + modifier_type, + prefetch_options_type, + export_options_type, + extern_options_type, + type_info_type, + manyptr_u8_type, + manyptr_const_u8_type, + fn_noreturn_no_args_type, + fn_void_no_args_type, + fn_naked_noreturn_no_args_type, + fn_ccc_void_no_args_type, + single_const_pointer_to_comptime_int_type, + const_slice_u8_type, + anyerror_void_error_union_type, + generic_poison_type, + + /// `undefined` (untyped) + undefined_value, + /// `0` (comptime_int) + zero, + /// `1` (comptime_int) + one, + /// `{}` + void_value, + /// `unreachable` (noreturn type) + unreachable_value, + /// `null` (untyped) + null_value, + /// `true` + bool_true, + /// `false` + bool_false, + /// `.{}` (untyped) + empty_aggregate, + the_only_possible_value, + generic_poison, + + unknown = std.math.maxInt(u32) - 1, + none = std.math.maxInt(u32), + _, + + pub fn fmtType(ty: Index, ip: InternPool) std.fmt.Formatter(Key.formatType) { + 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, + .ip = ip, + } }; + } +}; + +pub const NamespaceIndex = enum(u32) { + none = std.math.maxInt(u32), + _, +}; + +pub const Tag = enum(u8) { + /// A type that can be represented with only an enum tag. + /// data is SimpleType enum value + simple_type, + /// A value that can be represented with only an enum tag. + /// data is SimpleValue enum value + simple_value, + + /// An integer type. + /// data is number of bits + type_int_signed, + /// An integer type. + /// data is number of bits + type_int_unsigned, + /// A pointer type. + /// data is payload to Pointer. + type_pointer, + /// An array type. + /// data is payload to Array. + type_array, + /// An struct type. + /// data is payload to Struct. + type_struct, + /// An optional type. + /// data is index to type + type_optional, + /// An error union type. + /// data is payload to ErrorUnion. + type_error_union, + /// An error set type. + /// data is payload to ErrorSet. + type_error_set, + /// An enum type. + /// data is payload to Enum. + type_enum, + /// An function type. + /// data is payload to Function. + type_function, + /// An union type. + /// data is payload to Union. + type_union, + /// An tuple type. + /// data is payload to Tuple. + type_tuple, + /// An vector type. + /// data is payload to Vector. + type_vector, + /// An anyframe->T type. + /// 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, + /// An unsigned integer value that can be represented by u64. + /// data is payload to u64 + int_u64, + /// An unsigned integer value that can be represented by u64. + /// data is payload to i64 bitcasted to u64 + int_i64, + /// A positive integer value that does not fit in 64 bits. + /// data is a extra index to BigInt limbs. + int_big_positive, + /// A negative integer value that does not fit in 64 bits. + /// data is a extra index to BigInt limbs. + int_big_negative, + /// A float value that can be represented by f16. + /// data is f16 bitcasted to u16 cast to u32. + float_f16, + /// A float value that can be represented by f32. + /// data is f32 bitcasted to u32. + float_f32, + /// A float value that can be represented by f64. + /// data is payload to f64. + float_f64, + /// A float value that can be represented by f80. + /// data is payload to f80. + float_f80, + /// A float value that can be represented by f128. + /// data is payload to f128. + float_f128, + + /// A byte sequence value. + /// data is payload to data begin and length. + bytes, + /// A aggregate (struct) value. + /// data is index to Aggregate. + aggregate, + /// A union value. + /// data is index to UnionValue. + union_value, +}; + +pub const SimpleType = enum(u32) { + f16, + f32, + f64, + f80, + f128, + usize, + isize, + c_short, + c_ushort, + c_int, + c_uint, + c_long, + c_ulong, + c_longlong, + c_ulonglong, + c_longdouble, + anyopaque, + bool, + void, + type, + anyerror, + comptime_int, + comptime_float, + noreturn, + anyframe_type, + null_type, + undefined_type, + enum_literal_type, + + atomic_order, + atomic_rmw_op, + calling_convention, + address_space, + float_mode, + reduce_op, + modifier, + prefetch_options, + export_options, + extern_options, + type_info, + + generic_poison, +}; + +pub const SimpleValue = enum(u32) { + undefined_value, + void_value, + unreachable_value, + null_value, + bool_true, + bool_false, + the_only_possible_value, + generic_poison, +}; + +comptime { + assert(@sizeOf(SimpleType) == @sizeOf(SimpleValue)); +} + +pub fn init(gpa: Allocator) Allocator.Error!InternPool { + var ip: InternPool = .{}; + + const items = [_]struct { index: Index, key: Key }{ + .{ .index = .u1_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 1 } } }, + .{ .index = .u8_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 8 } } }, + .{ .index = .i8_type, .key = .{ .int_type = .{ .signedness = .signed, .bits = 8 } } }, + .{ .index = .u16_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 16 } } }, + .{ .index = .i16_type, .key = .{ .int_type = .{ .signedness = .signed, .bits = 16 } } }, + .{ .index = .u29_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 29 } } }, + .{ .index = .u32_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 32 } } }, + .{ .index = .i32_type, .key = .{ .int_type = .{ .signedness = .signed, .bits = 32 } } }, + .{ .index = .u64_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 64 } } }, + .{ .index = .i64_type, .key = .{ .int_type = .{ .signedness = .signed, .bits = 64 } } }, + .{ .index = .u128_type, .key = .{ .int_type = .{ .signedness = .unsigned, .bits = 128 } } }, + .{ .index = .i128_type, .key = .{ .int_type = .{ .signedness = .signed, .bits = 128 } } }, + + .{ .index = .usize_type, .key = .{ .simple_type = .usize } }, + .{ .index = .isize_type, .key = .{ .simple_type = .isize } }, + .{ .index = .c_short_type, .key = .{ .simple_type = .c_short } }, + .{ .index = .c_ushort_type, .key = .{ .simple_type = .c_ushort } }, + .{ .index = .c_int_type, .key = .{ .simple_type = .c_int } }, + .{ .index = .c_uint_type, .key = .{ .simple_type = .c_uint } }, + .{ .index = .c_long_type, .key = .{ .simple_type = .c_long } }, + .{ .index = .c_ulong_type, .key = .{ .simple_type = .c_ulong } }, + .{ .index = .c_longlong_type, .key = .{ .simple_type = .c_longlong } }, + .{ .index = .c_ulonglong_type, .key = .{ .simple_type = .c_ulonglong } }, + .{ .index = .c_longdouble_type, .key = .{ .simple_type = .c_longdouble } }, + .{ .index = .f16_type, .key = .{ .simple_type = .f16 } }, + .{ .index = .f32_type, .key = .{ .simple_type = .f32 } }, + .{ .index = .f64_type, .key = .{ .simple_type = .f64 } }, + .{ .index = .f80_type, .key = .{ .simple_type = .f80 } }, + .{ .index = .f128_type, .key = .{ .simple_type = .f128 } }, + .{ .index = .anyopaque_type, .key = .{ .simple_type = .anyopaque } }, + .{ .index = .bool_type, .key = .{ .simple_type = .bool } }, + .{ .index = .void_type, .key = .{ .simple_type = .void } }, + .{ .index = .type_type, .key = .{ .simple_type = .type } }, + .{ .index = .anyerror_type, .key = .{ .simple_type = .anyerror } }, + .{ .index = .comptime_int_type, .key = .{ .simple_type = .comptime_int } }, + .{ .index = .comptime_float_type, .key = .{ .simple_type = .comptime_float } }, + .{ .index = .noreturn_type, .key = .{ .simple_type = .noreturn } }, + .{ .index = .anyframe_type, .key = .{ .simple_type = .anyframe_type } }, + .{ .index = .null_type, .key = .{ .simple_type = .null_type } }, + .{ .index = .undefined_type, .key = .{ .simple_type = .undefined_type } }, + .{ .index = .enum_literal_type, .key = .{ .simple_type = .enum_literal_type } }, + + .{ .index = .atomic_order_type, .key = .{ .simple_type = .atomic_order } }, + .{ .index = .atomic_rmw_op_type, .key = .{ .simple_type = .atomic_rmw_op } }, + .{ .index = .calling_convention_type, .key = .{ .simple_type = .calling_convention } }, + .{ .index = .address_space_type, .key = .{ .simple_type = .address_space } }, + .{ .index = .float_mode_type, .key = .{ .simple_type = .float_mode } }, + .{ .index = .reduce_op_type, .key = .{ .simple_type = .reduce_op } }, + .{ .index = .modifier_type, .key = .{ .simple_type = .modifier } }, + .{ .index = .prefetch_options_type, .key = .{ .simple_type = .prefetch_options } }, + .{ .index = .export_options_type, .key = .{ .simple_type = .export_options } }, + .{ .index = .extern_options_type, .key = .{ .simple_type = .extern_options } }, + .{ .index = .type_info_type, .key = .{ .simple_type = .type_info } }, + .{ .index = .manyptr_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .size = .Many } } }, + .{ .index = .manyptr_const_u8_type, .key = .{ .pointer_type = .{ .elem_type = .u8_type, .size = .Many, .is_const = true } } }, + .{ .index = .fn_noreturn_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .noreturn_type } } }, + .{ .index = .fn_void_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type } } }, + .{ .index = .fn_naked_noreturn_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .calling_convention = .Naked } } }, + .{ .index = .fn_ccc_void_no_args_type, .key = .{ .function_type = .{ .args = &.{}, .return_type = .void_type, .calling_convention = .C } } }, + .{ .index = .single_const_pointer_to_comptime_int_type, .key = .{ .pointer_type = .{ .elem_type = .comptime_int_type, .size = .One, .is_const = true } } }, + .{ .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 = .undefined_value, .key = .{ .simple_value = .undefined_value } }, + .{ .index = .zero, .key = .{ .int_u64_value = 0 } }, + .{ .index = .one, .key = .{ .int_u64_value = 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 = .the_only_possible_value, .key = .{ .simple_value = .the_only_possible_value } }, + .{ .index = .generic_poison, .key = .{ .simple_value = .generic_poison } }, + }; + + const extra_count = 4 * @sizeOf(Pointer) + @sizeOf(ErrorUnion) + 4 * @sizeOf(Function); + + try ip.map.ensureTotalCapacity(gpa, items.len); + try ip.items.ensureTotalCapacity(gpa, items.len); + try ip.extra.ensureTotalCapacity(gpa, extra_count); + + for (items) |item| { + if (builtin.is_test or builtin.mode == .Debug) { + var failing_allocator = std.testing.FailingAllocator.init(undefined, 0); + std.testing.expectEqual(item.index, ip.get(failing_allocator.allocator(), item.key) catch unreachable) catch unreachable; + } else { + assert(item.index == ip.get(undefined, item.key) catch unreachable); + } + } + + return ip; +} + +pub fn deinit(ip: *InternPool, gpa: Allocator) void { + ip.map.deinit(gpa); + ip.items.deinit(gpa); + ip.extra.deinit(gpa); + + var struct_it = ip.structs.iterator(0); + while (struct_it.next()) |item| { + item.fields.deinit(gpa); + } + var enum_it = ip.enums.iterator(0); + while (enum_it.next()) |item| { + item.fields.deinit(gpa); + item.values.deinit(gpa); + } + var union_it = ip.unions.iterator(0); + while (union_it.next()) |item| { + item.fields.deinit(gpa); + } + ip.decls.deinit(gpa); + ip.structs.deinit(gpa); + ip.enums.deinit(gpa); + ip.unions.deinit(gpa); +} + +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) { + .simple_type => .{ .simple_type = @intToEnum(SimpleType, data) }, + .simple_value => .{ .simple_value = @intToEnum(SimpleValue, data) }, + + .type_int_signed => .{ .int_type = .{ + .signedness = .signed, + .bits = @intCast(u16, data), + } }, + .type_int_unsigned => .{ .int_type = .{ + .signedness = .unsigned, + .bits = @intCast(u16, data), + } }, + .type_pointer => .{ .pointer_type = ip.extraData(Pointer, data) }, + .type_array => .{ .array_type = ip.extraData(Array, data) }, + .type_optional => .{ .optional_type = .{ .payload_type = @intToEnum(Index, data) } }, + .type_anyframe => .{ .anyframe_type = .{ .child = @intToEnum(Index, data) } }, + .type_error_union => .{ .error_union_type = ip.extraData(ErrorUnion, data) }, + .type_error_set => .{ .error_set_type = ip.extraData(ErrorSet, data) }, + .type_function => .{ .function_type = ip.extraData(Function, data) }, + .type_tuple => .{ .tuple_type = ip.extraData(Tuple, data) }, + .type_vector => .{ .vector_type = ip.extraData(Vector, data) }, + + .type_struct => .{ .struct_type = @intToEnum(StructIndex, data) }, + .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), + } }, + .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) }, + + .bytes => .{ .bytes = ip.extraData([]const u8, 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, + }; +} + +pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index { + const adapter: KeyAdapter = .{ .ip = ip }; + const gop = try ip.map.getOrPutAdapted(gpa, key, adapter); + if (gop.found_existing) return @intToEnum(Index, gop.index); + + const tag: Tag = key.tag(); + const data: u32 = switch (key) { + .simple_type => |simple| @enumToInt(simple), + .simple_value => |simple| @enumToInt(simple), + + .int_type => |int_ty| int_ty.bits, + .optional_type => |optional_ty| @enumToInt(optional_ty.payload_type), + .anyframe_type => |anyframe_ty| @enumToInt(anyframe_ty.child), + + .struct_type => |struct_index| @enumToInt(struct_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), + .float_16_value => |float_val| @bitCast(u16, float_val), + .float_32_value => |float_val| @bitCast(u32, float_val), + inline else => |data| try ip.addExtra(gpa, data), // TODO sad stage1 noises :( + }; + + try ip.items.append(gpa, .{ + .tag = tag, + .data = data, + }); + return @intToEnum(Index, ip.items.len - 1); +} + +pub fn contains(ip: InternPool, key: Key) ?Index { + const adapter: KeyAdapter = .{ .ip = &ip }; + const index = ip.map.getIndexAdapted(key, adapter) orelse return null; + return @intToEnum(Index, index); +} + +pub fn getDecl(ip: InternPool, index: InternPool.DeclIndex) *InternPool.Decl { + var decls = ip.decls; + return decls.at(@enumToInt(index)); +} +pub fn getStruct(ip: InternPool, index: InternPool.StructIndex) *InternPool.Struct { + var structs = ip.structs; + return structs.at(@enumToInt(index)); +} +pub fn getEnum(ip: InternPool, index: InternPool.EnumIndex) *InternPool.Enum { + var enums = ip.enums; + return enums.at(@enumToInt(index)); +} +pub fn getUnion(ip: InternPool, index: InternPool.UnionIndex) *InternPool.Union { + var unions = ip.unions; + return unions.at(@enumToInt(index)); +} + +pub fn createDecl(ip: *InternPool, gpa: Allocator, decl: InternPool.Decl) Allocator.Error!InternPool.DeclIndex { + try ip.decls.append(gpa, decl); + return @intToEnum(InternPool.DeclIndex, ip.decls.count() - 1); +} +pub fn createStruct(ip: *InternPool, gpa: Allocator, struct_info: InternPool.Struct) Allocator.Error!InternPool.StructIndex { + try ip.structs.append(gpa, struct_info); + return @intToEnum(InternPool.StructIndex, ip.structs.count() - 1); +} +pub fn createEnum(ip: *InternPool, gpa: Allocator, enum_info: InternPool.Enum) Allocator.Error!InternPool.EnumIndex { + try ip.enums.append(gpa, enum_info); + return @intToEnum(InternPool.EnumIndex, ip.enums.count() - 1); +} +pub fn createUnion(ip: *InternPool, gpa: Allocator, union_info: InternPool.Union) Allocator.Error!InternPool.UnionIndex { + try ip.unions.append(gpa, union_info); + return @intToEnum(InternPool.UnionIndex, ip.unions.count() - 1); +} + +fn addExtra(ip: *InternPool, gpa: Allocator, extra: anytype) Allocator.Error!u32 { + const T = @TypeOf(extra); + comptime if (@sizeOf(T) <= 4) { + @compileError(@typeName(T) ++ " fits into a u32! Consider directly storing this extra in Item's data field"); + }; + + const result = @intCast(u32, ip.extra.items.len); + var managed = ip.extra.toManaged(gpa); + defer ip.extra = managed.moveToUnmanaged(); + try encoding.encode(&managed, T, extra); + return result; +} + +fn extraData(ip: InternPool, comptime T: type, index: usize) T { + var bytes: []const u8 = ip.extra.items[index..]; + return encoding.decode(&bytes, T); +} + +const KeyAdapter = struct { + ip: *const InternPool, + + pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool { + _ = b_void; + return a.eql(ctx.ip.indexToKey(@intToEnum(Index, b_map_index))); + } + + pub fn hash(ctx: @This(), a: Key) u32 { + _ = ctx; + return a.hash(); + } +}; + +fn deepEql(a: anytype, b: @TypeOf(a)) bool { + const T = @TypeOf(a); + + switch (@typeInfo(T)) { + .Struct => |info| { + if (info.layout == .Packed and comptime std.meta.trait.hasUniqueRepresentation(T)) { + return std.mem.eql(u8, std.mem.asBytes(&a), std.mem.asBytes(&b)); + } + inline for (info.fields) |field_info| { + if (!deepEql(@field(a, field_info.name), @field(b, field_info.name))) return false; + } + return true; + }, + .Union => |info| { + const UnionTag = info.tag_type.?; + + const tag_a = std.meta.activeTag(a); + const tag_b = std.meta.activeTag(b); + if (tag_a != tag_b) return false; + + inline for (info.fields) |field_info| { + if (@field(UnionTag, field_info.name) == tag_a) { + return deepEql(@field(a, field_info.name), @field(b, field_info.name)); + } + } + return false; + }, + .Pointer => |info| switch (info.size) { + .One => return deepEql(a.*, b.*), + .Slice => { + if (a.len != b.len) return false; + + var i: usize = 0; + while (i < a.len) : (i += 1) { + if (!deepEql(a[i], b[i])) return false; + } + return true; + }, + .Many, + .C, + => @compileError("Unable to equality compare pointer " ++ @typeName(T)), + }, + .Float => { + const I = std.meta.Int(.unsigned, @bitSizeOf(T)); + return @bitCast(I, a) == @bitCast(I, b); + }, + .Bool, + .Int, + .Enum, + => return a == b, + else => @compileError("Unable to equality compare type " ++ @typeName(T)), + } +} + +fn deepHash(hasher: anytype, key: anytype) void { + const T = @TypeOf(key); + + switch (@typeInfo(T)) { + .Int => { + if (comptime std.meta.trait.hasUniqueRepresentation(Tuple)) { + hasher.update(std.mem.asBytes(&key)); + } else { + const byte_size = comptime std.math.divCeil(comptime_int, @bitSizeOf(T), 8) catch unreachable; + hasher.update(std.mem.asBytes(&key)[0..byte_size]); + } + }, + + .Bool => deepHash(hasher, @boolToInt(key)), + .Enum => deepHash(hasher, @enumToInt(key)), + .Float => |info| deepHash(hasher, switch (info.bits) { + 16 => @bitCast(u16, key), + 32 => @bitCast(u32, key), + 64 => @bitCast(u64, key), + 80 => @bitCast(u80, key), + 128 => @bitCast(u128, key), + else => unreachable, + }), + + .Pointer => |info| switch (info.size) { + .One => { + deepHash(hasher, key.*); + }, + .Slice => { + if (info.child == u8) { + hasher.update(key); + } else { + for (key) |item| { + deepHash(hasher, item); + } + } + }, + .Many, + .C, + => @compileError("Unable to hash pointer " ++ @typeName(T)), + }, + .Struct => |info| { + if (info.layout == .Packed and comptime std.meta.trait.hasUniqueRepresentation(T)) { + hasher.update(std.mem.asBytes(&key)); + } else { + inline for (info.fields) |field| { + deepHash(hasher, @field(key, field.name)); + } + } + }, + + .Union => |info| { + const TagType = info.tag_type.?; + + const tag = std.meta.activeTag(key); + deepHash(hasher, tag); + inline for (info.fields) |field| { + if (@field(TagType, field.name) == tag) { + deepHash(hasher, @field(key, field.name)); + break; + } + } + }, + else => @compileError("Unable to hash type " ++ @typeName(T)), + } +} + +// --------------------------------------------- +// UTILITY +// --------------------------------------------- + +pub fn cast(ip: *InternPool, gpa: Allocator, destination_ty: Index, source_ty: Index, target: std.Target) Allocator.Error!Index { + return resolvePeerTypes(ip, gpa, &.{ destination_ty, source_ty }, target); +} + +pub fn resolvePeerTypes(ip: *InternPool, gpa: Allocator, types: []const Index, target: std.Target) Allocator.Error!Index { + switch (types.len) { + 0 => return Index.noreturn_type, + 1 => return types[0], + else => {}, + } + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + var arena = arena_allocator.allocator(); + + var chosen = types[0]; + // If this is non-null then it does the following thing, depending on the chosen zigTypeTag(). + // * ErrorSet: this is an override + // * ErrorUnion: this is an override of the error set only + // * other: at the end we make an ErrorUnion with the other thing and this + var err_set_ty: Index = Index.none; + var any_are_null = false; + var seen_const = false; + var convert_to_slice = false; + var chosen_i: usize = 0; + for (types[1..]) |candidate, candidate_i| { + if (candidate == chosen) continue; + + const candidate_key: Key = ip.indexToKey(candidate); + const chosen_key = ip.indexToKey(chosen); + + // If the candidate can coerce into our chosen type, we're done. + // If the chosen type can coerce into the candidate, use that. + if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen, candidate, true, target)) == .ok) { + continue; + } + if ((try ip.coerceInMemoryAllowed(gpa, arena, candidate, chosen, true, target)) == .ok) { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + + switch (candidate_key) { + .simple_type => |candidate_simple| switch (candidate_simple) { + .f16, .f32, .f64, .f80, .f128 => switch (chosen_key) { + .simple_type => |chosen_simple| switch (chosen_simple) { + .f16, .f32, .f64, .f80, .f128 => { + if (chosen_key.floatBits(target) < candidate_key.floatBits(target)) { + chosen = candidate; + chosen_i = candidate_i + 1; + } + continue; + }, + .comptime_int, .comptime_float => { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + }, + else => {}, + }, + else => {}, + }, + + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + => switch (chosen_key) { + .simple_type => |chosen_simple| switch (chosen_simple) { + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + => { + 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; + chosen_i = candidate_i + 1; + } + continue; + }, + .comptime_int => { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + }, + else => {}, + }, + .int_type => |chosen_info| { + if (chosen_info.bits < candidate_key.intInfo(target, ip).bits) { + chosen = candidate; + chosen_i = candidate_i + 1; + } + continue; + }, + .pointer_type => |chosen_info| if (chosen_info.size == .C) continue, + else => {}, + }, + + .noreturn, .undefined_type => continue, + + .comptime_int => switch (chosen_key) { + .simple_type => |chosen_simple| switch (chosen_simple) { + .f16, + .f32, + .f64, + .f80, + .f128, + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .comptime_float, + => continue, + .comptime_int => unreachable, + else => {}, + }, + .int_type => continue, + .pointer_type => |chosen_info| if (chosen_info.size == .C) continue, + else => {}, + }, + .comptime_float => switch (chosen_key) { + .simple_type => |chosen_simple| switch (chosen_simple) { + .f16, .f32, .f64, .f80, .f128 => continue, + .comptime_int => { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + }, + .comptime_float => unreachable, + else => {}, + }, + else => {}, + }, + .null_type => { + any_are_null = true; + continue; + }, + else => {}, + }, + .int_type => |candidate_info| switch (chosen_key) { + .simple_type => |chosen_simple| switch (chosen_simple) { + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + => { + 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; + chosen_i = candidate_i + 1; + } + continue; + }, + .comptime_int => { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + }, + else => {}, + }, + .int_type => |chosen_info| { + if (chosen_info.bits < candidate_info.bits) { + chosen = candidate; + chosen_i = candidate_i + 1; + } + continue; + }, + .pointer_type => |chosen_info| if (chosen_info.size == .C) continue, + else => {}, + }, + .pointer_type => |candidate_info| switch (chosen_key) { + .simple_type => |chosen_simple| switch (chosen_simple) { + .comptime_int => { + if (candidate_info.size == .C) { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + }, + else => {}, + }, + .pointer_type => |chosen_info| { + seen_const = seen_const or chosen_info.is_const or candidate_info.is_const; + + const candidate_elem_info = ip.indexToKey(candidate_info.elem_type); + const chosen_elem_info = ip.indexToKey(chosen_info.elem_type); + + // *[N]T to [*]T + // *[N]T to []T + if ((candidate_info.size == .Many or candidate_info.size == .Slice) and + chosen_info.size == .One and + chosen_elem_info == .array_type) + { + // In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T` + convert_to_slice = false; + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + if (candidate_info.size == .One and + candidate_elem_info == .array_type and + (chosen_info.size == .Many or chosen_info.size == .Slice)) + { + // In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T` + convert_to_slice = false; + continue; + } + + // *[N]T and *[M]T + // Verify both are single-pointers to arrays. + // Keep the one whose element type can be coerced into. + if (chosen_info.size == .One and + candidate_info.size == .One and + chosen_elem_info == .array_type and + candidate_elem_info == .array_type) + { + const chosen_elem_ty = chosen_elem_info.array_type.child; + const cand_elem_ty = candidate_elem_info.array_type.child; + + const chosen_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, chosen_elem_ty, cand_elem_ty, chosen_info.is_const, target); + if (chosen_ok) { + convert_to_slice = true; + continue; + } + + const cand_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, cand_elem_ty, chosen_elem_ty, candidate_info.is_const, target); + if (cand_ok) { + convert_to_slice = true; + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + + // They're both bad. Report error. + // In the future we probably want to use the + // coerceInMemoryAllowed error reporting mechanism, + // however, for now we just fall through for the + // "incompatible types" error below. + } + + // [*c]T and any other pointer size + // Whichever element type can coerce to the other one, is + // the one we will keep. If they're both OK then we keep the + // C pointer since it matches both single and many pointers. + if (candidate_info.size == .C or chosen_info.size == .C) { + const cand_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, candidate_info.elem_type, chosen_info.elem_type, candidate_info.is_const, target); + const chosen_ok = .ok == try ip.coerceInMemoryAllowed(gpa, arena, chosen_info.elem_type, candidate_info.elem_type, chosen_info.is_const, target); + + if (cand_ok) { + if (!chosen_ok or chosen_info.size != .C) { + chosen = candidate; + chosen_i = candidate_i + 1; + } + continue; + } else { + if (chosen_ok) continue; + // They're both bad. Report error. + // In the future we probably want to use the + // coerceInMemoryAllowed error reporting mechanism, + // however, for now we just fall through for the + // "incompatible types" error below. + } + } + }, + .int_type => { + if (candidate_info.size == .C) { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + }, + .optional_type => |chosen_info| switch (ip.indexToKey(chosen_info.payload_type)) { + .pointer_type => |chosen_ptr_info| { + seen_const = seen_const or chosen_ptr_info.is_const or candidate_info.is_const; + + // *[N]T to ?![*]T + // *[N]T to ?![]T + if (candidate_info.size == .One and + ip.indexToKey(candidate_info.elem_type) == .array_type and + (chosen_ptr_info.size == .Many or chosen_ptr_info.size == .Slice)) + { + continue; + } + }, + else => {}, + }, + .error_union_type => |chosen_info| { + const chosen_ptr_key = ip.indexToKey(chosen_info.payload_type); + + if (chosen_ptr_key == .pointer_type) { + const chosen_ptr_info = chosen_ptr_key.pointer_type; + seen_const = seen_const or chosen_ptr_info.is_const or candidate_info.is_const; + + // *[N]T to E![*]T + // *[N]T to E![]T + if (candidate_info.size == .One and + (chosen_ptr_info.size == .Many or chosen_ptr_info.size == .Slice) and + ip.indexToKey(candidate_info.elem_type) == .array_type) + { + continue; + } + } + }, + .function_type => |chosen_info| { + if (candidate_info.is_const) { + const candidate_elem_key = ip.indexToKey(candidate_info.elem_type); + if (candidate_elem_key == .function_type and + .ok == try ip.coerceInMemoryAllowedFns(gpa, arena, chosen_info, candidate_elem_key.function_type, target)) + { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + } + }, + else => {}, + }, + .array_type => switch (chosen_key) { + .vector_type => continue, + else => {}, + }, + .optional_type => |candidate_info| { + if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen, candidate_info.payload_type, true, target)) == .ok) { + seen_const = seen_const or ip.indexToKey(candidate_info.payload_type).isConstPtr(); + any_are_null = true; + continue; + } + + seen_const = seen_const or chosen_key.isConstPtr(); + any_are_null = false; + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + }, + .vector_type => switch (chosen_key) { + .array_type => { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + }, + else => {}, + }, + else => {}, + } + + switch (chosen_key) { + .simple_type => |simple| switch (simple) { + .noreturn, + .undefined_type, + => { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + }, + .null_type => { + any_are_null = true; + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + }, + else => {}, + }, + .optional_type => |chosen_info| { + if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen_info.payload_type, candidate, true, target)) == .ok) { + continue; + } + if ((try ip.coerceInMemoryAllowed(gpa, arena, candidate, chosen_info.payload_type, true, target)) == .ok) { + any_are_null = true; + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + }, + .error_union_type => |chosen_info| { + if ((try ip.coerceInMemoryAllowed(gpa, arena, chosen_info.payload_type, candidate, true, target)) == .ok) { + continue; + } + }, + else => {}, + } + + return Index.none; + } + + if (chosen == .none) return chosen; + const chosen_key = ip.indexToKey(chosen); + + if (convert_to_slice) { + // turn *[N]T => []T + const chosen_elem_key = ip.indexToKey(chosen_key.pointer_type.elem_type); + var info = chosen_key.pointer_type; + info.sentinel = chosen_elem_key.sentinel(); + info.size = .Slice; + info.is_const = seen_const or chosen_elem_key.isConstPtr(); + info.elem_type = chosen_elem_key.elemType2(); + + const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info }); + const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty; + const set_ty = if (err_set_ty != .none) err_set_ty else return opt_ptr_ty; + return try ip.get(gpa, .{ .error_union_type = .{ + .error_set_type = set_ty, + .payload_type = opt_ptr_ty, + } }); + } + + if (seen_const) { + // turn []T => []const T + switch (chosen_key) { + .error_union_type => |error_union_info| { + var info: Pointer = ip.indexToKey(error_union_info.payload_type).pointer_type; + info.is_const = true; + + const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info }); + const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty; + const set_ty = if (err_set_ty != .none) err_set_ty else error_union_info.error_set_type; + return try ip.get(gpa, .{ .error_union_type = .{ + .error_set_type = set_ty, + .payload_type = opt_ptr_ty, + } }); + }, + .pointer_type => |pointer_info| { + var info = pointer_info; + info.is_const = true; + + const new_ptr_ty = try ip.get(gpa, .{ .pointer_type = info }); + const opt_ptr_ty = if (any_are_null) try ip.get(gpa, .{ .optional_type = .{ .payload_type = new_ptr_ty } }) else new_ptr_ty; + const set_ty = if (err_set_ty != .none) err_set_ty else return opt_ptr_ty; + return try ip.get(gpa, .{ .error_union_type = .{ + .error_set_type = set_ty, + .payload_type = opt_ptr_ty, + } }); + }, + else => return chosen, + } + } + + if (any_are_null) { + const opt_ty = switch (chosen_key) { + .simple_type => |simple| switch (simple) { + .null_type => chosen, + else => try ip.get(gpa, .{ .optional_type = .{ .payload_type = chosen } }), + }, + .optional_type => chosen, + else => try ip.get(gpa, .{ .optional_type = .{ .payload_type = chosen } }), + }; + const set_ty = if (err_set_ty != .none) err_set_ty else return opt_ty; + return try ip.get(gpa, .{ .error_union_type = .{ + .error_set_type = set_ty, + .payload_type = opt_ty, + } }); + } + + return chosen; +} + +const InMemoryCoercionResult = union(enum) { + ok, + no_match: Pair, + int_not_coercible: IntMismatch, + error_union_payload: PairAndChild, + array_len: IntPair, + array_sentinel: Sentinel, + array_elem: PairAndChild, + vector_len: IntPair, + vector_elem: PairAndChild, + optional_shape: Pair, + optional_child: PairAndChild, + from_anyerror, + missing_error: []const Index, + /// true if wanted is var args + fn_var_args: bool, + /// true if wanted is generic + fn_generic: bool, + fn_param_count: IntPair, + fn_param_noalias: IntPair, + fn_param_comptime: ComptimeParam, + fn_param: Param, + fn_cc: CC, + fn_return_type: PairAndChild, + ptr_child: PairAndChild, + ptr_addrspace: AddressSpace, + ptr_sentinel: Sentinel, + ptr_size: Size, + ptr_qualifiers: Qualifiers, + ptr_allowzero: Pair, + ptr_bit_range: BitRange, + ptr_alignment: IntPair, + + const Pair = struct { + actual: Index, // type + wanted: Index, // type + }; + + const PairAndChild = struct { + child: *InMemoryCoercionResult, + actual: Index, // type + wanted: Index, // type + }; + + const Param = struct { + child: *InMemoryCoercionResult, + actual: Index, // type + wanted: Index, // type + index: u64, + }; + + const ComptimeParam = struct { + index: u64, + wanted: bool, + }; + + const Sentinel = struct { + // Index.none indicates no sentinel + actual: Index, // value + wanted: Index, // value + ty: Index, + }; + + const IntMismatch = struct { + actual_signedness: std.builtin.Signedness, + wanted_signedness: std.builtin.Signedness, + actual_bits: u16, + wanted_bits: u16, + }; + + const IntPair = struct { + actual: u64, + wanted: u64, + }; + + const Size = struct { + actual: std.builtin.Type.Pointer.Size, + wanted: std.builtin.Type.Pointer.Size, + }; + + const Qualifiers = struct { + actual_const: bool, + wanted_const: bool, + actual_volatile: bool, + wanted_volatile: bool, + }; + + const AddressSpace = struct { + actual: std.builtin.AddressSpace, + wanted: std.builtin.AddressSpace, + }; + + const CC = struct { + actual: std.builtin.CallingConvention, + wanted: std.builtin.CallingConvention, + }; + + const BitRange = struct { + actual_host: u16, + wanted_host: u16, + actual_offset: u16, + wanted_offset: u16, + }; + + fn dupe(child: *const InMemoryCoercionResult, arena: Allocator) !*InMemoryCoercionResult { + const res = try arena.create(InMemoryCoercionResult); + res.* = child.*; + return res; + } +}; + +/// If types have the same representation in runtime memory +/// * int/float: same number of bits +/// * pointer: see `coerceInMemoryAllowedPtrs` +/// * error union: coerceable error set and payload +/// * error set: sub-set to super-set +/// * array: same shape and coerceable child +fn coerceInMemoryAllowed( + ip: *InternPool, + gpa: Allocator, + arena: Allocator, + dest_ty: Index, + src_ty: Index, + dest_is_const: bool, + target: std.Target, +) Allocator.Error!InMemoryCoercionResult { + if (dest_ty == src_ty) return .ok; + + const dest_key = ip.indexToKey(dest_ty); + const src_key = ip.indexToKey(src_ty); + + const dest_tag = dest_key.zigTypeTag(); + const src_tag = src_key.zigTypeTag(); + + if (dest_tag != src_tag) { + return InMemoryCoercionResult{ .no_match = .{ + .actual = dest_ty, + .wanted = src_ty, + } }; + } + + switch (dest_tag) { + .Int => { + 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; + + if ((src_info.signedness == dest_info.signedness and dest_info.bits < src_info.bits) or + // small enough unsigned ints can get casted to large enough signed ints + (dest_info.signedness == .signed and (src_info.signedness == .unsigned or dest_info.bits <= src_info.bits)) or + (dest_info.signedness == .unsigned and src_info.signedness == .signed)) + { + return InMemoryCoercionResult{ .int_not_coercible = .{ + .actual_signedness = src_info.signedness, + .wanted_signedness = dest_info.signedness, + .actual_bits = src_info.bits, + .wanted_bits = dest_info.bits, + } }; + } + return .ok; + }, + .Float => { + const dest_bits = dest_key.floatBits(target); + const src_bits = src_key.floatBits(target); + if (dest_bits == src_bits) return .ok; + return InMemoryCoercionResult{ .no_match = .{ + .actual = dest_ty, + .wanted = src_ty, + } }; + }, + .Pointer => { + return try ip.coerceInMemoryAllowedPtrs(gpa, arena, dest_ty, src_ty, dest_key, src_key, dest_is_const, target); + }, + .Optional => { + // Pointer-like Optionals + const maybe_dest_ptr_ty = try ip.optionalPtrTy(dest_key); + const maybe_src_ptr_ty = try ip.optionalPtrTy(src_key); + if (maybe_dest_ptr_ty != .none and maybe_src_ptr_ty != .none) { + const dest_ptr_info = ip.indexToKey(maybe_dest_ptr_ty); + const src_ptr_info = ip.indexToKey(maybe_src_ptr_ty); + return try ip.coerceInMemoryAllowedPtrs(gpa, arena, dest_ty, src_ty, dest_ptr_info, src_ptr_info, dest_is_const, target); + } + + if (maybe_dest_ptr_ty != maybe_src_ptr_ty) { + return InMemoryCoercionResult{ .optional_shape = .{ + .actual = src_ty, + .wanted = dest_ty, + } }; + } + + const dest_child_type = dest_key.optional_type.payload_type; + const src_child_type = src_key.optional_type.payload_type; + + const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_child_type, src_child_type, dest_is_const, target); + if (child != .ok) { + return InMemoryCoercionResult{ .optional_child = .{ + .child = try child.dupe(arena), + .actual = src_child_type, + .wanted = dest_child_type, + } }; + } + + return .ok; + }, + .Fn => { + return try ip.coerceInMemoryAllowedFns(gpa, arena, dest_key.function_type, src_key.function_type, target); + }, + .ErrorUnion => { + const dest_payload = dest_key.error_union_type.payload_type; + const src_payload = src_key.error_union_type.payload_type; + const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_payload, src_payload, dest_is_const, target); + if (child != .ok) { + return InMemoryCoercionResult{ .error_union_payload = .{ + .child = try child.dupe(arena), + .actual = src_payload, + .wanted = dest_payload, + } }; + } + const dest_set = dest_key.error_union_type.error_set_type; + const src_set = src_key.error_union_type.error_set_type; + return try ip.coerceInMemoryAllowed(gpa, arena, dest_set, src_set, dest_is_const, target); + }, + .ErrorSet => { + return try ip.coerceInMemoryAllowedErrorSets(gpa, arena, dest_ty, src_ty); + }, + .Array => { + const dest_info = dest_key.array_type; + const src_info = src_key.array_type; + if (dest_info.len != src_info.len) { + return InMemoryCoercionResult{ .array_len = .{ + .actual = src_info.len, + .wanted = dest_info.len, + } }; + } + + const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.child, src_info.child, dest_is_const, target); + if (child != .ok) { + return InMemoryCoercionResult{ .array_elem = .{ + .child = try child.dupe(arena), + .actual = src_info.child, + .wanted = dest_info.child, + } }; + } + + const ok_sent = dest_info.sentinel == Index.none or + (src_info.sentinel != Index.none and + dest_info.sentinel == src_info.sentinel // is this enough for a value equality check? + ); + if (!ok_sent) { + return InMemoryCoercionResult{ .array_sentinel = .{ + .actual = src_info.sentinel, + .wanted = dest_info.sentinel, + .ty = dest_info.child, + } }; + } + return .ok; + }, + .Vector => { + const dest_len = dest_key.vector_type.len; + const src_len = src_key.vector_type.len; + + if (dest_len != src_len) { + return InMemoryCoercionResult{ .vector_len = .{ + .actual = src_len, + .wanted = dest_len, + } }; + } + + const dest_elem_ty = dest_key.vector_type.child; + const src_elem_ty = src_key.vector_type.child; + const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_elem_ty, src_elem_ty, dest_is_const, target); + if (child != .ok) { + return InMemoryCoercionResult{ .vector_elem = .{ + .child = try child.dupe(arena), + .actual = src_elem_ty, + .wanted = dest_elem_ty, + } }; + } + + return .ok; + }, + else => { + return InMemoryCoercionResult{ .no_match = .{ + .actual = dest_ty, + .wanted = src_ty, + } }; + }, + } +} + +fn coerceInMemoryAllowedErrorSets( + ip: *InternPool, + gpa: Allocator, + arena: Allocator, + dest_ty: Index, + src_ty: Index, +) !InMemoryCoercionResult { + if (dest_ty == src_ty) return .ok; + + const dest_key = ip.indexToKey(dest_ty); + assert(dest_key.zigTypeTag() == .ErrorSet); + + if (dest_ty == .anyerror_type) return .ok; + + const src_key = ip.indexToKey(src_ty); + assert(src_key.zigTypeTag() == .ErrorSet); + + if (src_ty == .anyerror_type) return .from_anyerror; + + var missing_error_buf = std.ArrayListUnmanaged(Index){}; + defer missing_error_buf.deinit(gpa); + + for (src_key.error_set_type.names) |name| { + if (std.mem.indexOfScalar(Index, dest_key.error_set_type.names, name) == null) { + try missing_error_buf.append(gpa, name); + } + } + + if (missing_error_buf.items.len == 0) return .ok; + + return InMemoryCoercionResult{ + .missing_error = try arena.dupe(Index, missing_error_buf.items), + }; +} + +fn coerceInMemoryAllowedFns( + ip: *InternPool, + gpa: Allocator, + arena: Allocator, + dest_info: Function, + src_info: Function, + target: std.Target, +) Allocator.Error!InMemoryCoercionResult { + if (dest_info.is_var_args != src_info.is_var_args) { + return InMemoryCoercionResult{ .fn_var_args = dest_info.is_var_args }; + } + + if (dest_info.is_generic != src_info.is_generic) { + return InMemoryCoercionResult{ .fn_generic = dest_info.is_generic }; + } + + if (dest_info.calling_convention != src_info.calling_convention) { + return InMemoryCoercionResult{ .fn_cc = .{ + .actual = src_info.calling_convention, + .wanted = dest_info.calling_convention, + } }; + } + + if (src_info.return_type != Index.noreturn_type) { + const rt = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.return_type, src_info.return_type, true, target); + if (rt != .ok) { + return InMemoryCoercionResult{ .fn_return_type = .{ + .child = try rt.dupe(arena), + .actual = src_info.return_type, + .wanted = dest_info.return_type, + } }; + } + } + + if (dest_info.args.len != src_info.args.len) { + return InMemoryCoercionResult{ .fn_param_count = .{ + .actual = src_info.args.len, + .wanted = dest_info.args.len, + } }; + } + + if (!dest_info.args_is_noalias.eql(src_info.args_is_noalias)) { + return InMemoryCoercionResult{ .fn_param_noalias = .{ + .actual = src_info.args_is_noalias.mask, + .wanted = dest_info.args_is_noalias.mask, + } }; + } + + if (!dest_info.args_is_comptime.eql(src_info.args_is_comptime)) { + const index = dest_info.args_is_comptime.xorWith(src_info.args_is_comptime).findFirstSet().?; + return InMemoryCoercionResult{ .fn_param_comptime = .{ + .index = index, + .wanted = dest_info.args_is_comptime.isSet(index), + } }; + } + + for (dest_info.args) |dest_arg_ty, i| { + const src_arg_ty = src_info.args[i]; + + // Note: Cast direction is reversed here. + const param = try ip.coerceInMemoryAllowed(gpa, arena, src_arg_ty, dest_arg_ty, true, target); + if (param != .ok) { + return InMemoryCoercionResult{ .fn_param = .{ + .child = try param.dupe(arena), + .actual = src_arg_ty, + .wanted = dest_arg_ty, + .index = i, + } }; + } + } + + return .ok; +} + +/// If pointers have the same representation in runtime memory +/// * `const` attribute can be gained +/// * `volatile` attribute can be gained +/// * `allowzero` attribute can be gained (whether from explicit attribute, C pointer, or optional pointer) but only if dest_is_const +/// * alignment can be decreased +/// * bit offset attributes must match exactly +/// * `*`/`[*]` must match exactly, but `[*c]` matches either one +/// * sentinel-terminated pointers can coerce into `[*]` +fn coerceInMemoryAllowedPtrs( + ip: *InternPool, + gpa: Allocator, + arena: Allocator, + dest_ty: Index, + src_ty: Index, + dest_ptr_info: Key, + src_ptr_info: Key, + dest_is_const: bool, + target: std.Target, +) Allocator.Error!InMemoryCoercionResult { + const dest_info = dest_ptr_info.pointer_type; + const src_info = src_ptr_info.pointer_type; + + const ok_ptr_size = src_info.size == dest_info.size or + src_info.size == .C or dest_info.size == .C; + if (!ok_ptr_size) { + return InMemoryCoercionResult{ .ptr_size = .{ + .actual = src_info.size, + .wanted = dest_info.size, + } }; + } + + const ok_cv_qualifiers = + (!src_info.is_const or dest_info.is_const) and + (!src_info.is_volatile or dest_info.is_volatile); + + if (!ok_cv_qualifiers) { + return InMemoryCoercionResult{ .ptr_qualifiers = .{ + .actual_const = src_info.is_const, + .wanted_const = dest_info.is_const, + .actual_volatile = src_info.is_volatile, + .wanted_volatile = dest_info.is_volatile, + } }; + } + + if (dest_info.address_space != src_info.address_space) { + return InMemoryCoercionResult{ .ptr_addrspace = .{ + .actual = src_info.address_space, + .wanted = dest_info.address_space, + } }; + } + + const child = try ip.coerceInMemoryAllowed(gpa, arena, dest_info.elem_type, src_info.elem_type, dest_info.is_const, target); + if (child != .ok) { + return InMemoryCoercionResult{ .ptr_child = .{ + .child = try child.dupe(arena), + .actual = src_info.elem_type, + .wanted = dest_info.elem_type, + } }; + } + + const dest_allow_zero = dest_ptr_info.ptrAllowsZero(ip); + const src_allow_zero = src_ptr_info.ptrAllowsZero(ip); + + const ok_allows_zero = (dest_allow_zero and (src_allow_zero or dest_is_const)) or (!dest_allow_zero and !src_allow_zero); + if (!ok_allows_zero) { + return InMemoryCoercionResult{ .ptr_allowzero = .{ + .actual = src_ty, + .wanted = dest_ty, + } }; + } + + if (src_info.host_size != dest_info.host_size or + src_info.bit_offset != dest_info.bit_offset) + { + return InMemoryCoercionResult{ .ptr_bit_range = .{ + .actual_host = src_info.host_size, + .wanted_host = dest_info.host_size, + .actual_offset = src_info.bit_offset, + .wanted_offset = dest_info.bit_offset, + } }; + } + + const ok_sent = dest_info.sentinel == .none or src_info.size == .C or dest_info.sentinel == src_info.sentinel; // is this enough for a value equality check? + if (!ok_sent) { + return InMemoryCoercionResult{ .ptr_sentinel = .{ + .actual = src_info.sentinel, + .wanted = dest_info.sentinel, + .ty = dest_info.elem_type, + } }; + } + + // If both pointers have alignment 0, it means they both want ABI alignment. + // In this case, if they share the same child type, no need to resolve + // pointee type alignment. Otherwise both pointee types must have their alignment + // resolved and we compare the alignment numerically. + alignment: { + if (src_info.alignment == 0 and dest_info.alignment == 0 and + dest_info.elem_type == src_info.elem_type // is this enough for a value equality check? + ) { + break :alignment; + } + + // const src_align = if (src_info.alignment != 0) + // src_info.alignment + // else + // src_info.elem_type.abiAlignment(target); + + // const dest_align = if (dest_info.alignment != 0) + // dest_info.alignment + // else + // dest_info.elem_type.abiAlignment(target); + + // if (dest_align > src_align) { + // return InMemoryCoercionResult{ .ptr_alignment = .{ + // .actual = src_align, + // .wanted = dest_align, + // } }; + // } + + break :alignment; + } + + return .ok; +} + +fn optionalPtrTy( + ip: InternPool, + ty: Key, +) !Index { + switch (ty) { + .optional_type => |optional_info| { + const child_type = optional_info.payload_type; + const child_key = ip.indexToKey(child_type); + + if (child_key != .pointer_type) return Index.none; + const child_ptr_key = child_key.pointer_type; + + switch (child_ptr_key.size) { + .Slice, .C => return Index.none, + .Many, .One => { + if (child_ptr_key.is_allowzero) return Index.none; + + // optionals of zero sized types behave like bools, not pointers + if (child_key.onePossibleValue(ip) != Index.none) return Index.none; + + return child_type; + }, + } + }, + else => unreachable, + } +} + +/// will panic in during testing else will return `value` +inline fn panicOrElse(message: []const u8, value: anytype) @TypeOf(value) { + if (builtin.is_test) { + @panic(message); + } + return 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; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const null_type = try ip.get(gpa, .{ .simple_type = .null_type }); + const undefined_type = try ip.get(gpa, .{ .simple_type = .undefined_type }); + const enum_literal_type = try ip.get(gpa, .{ .simple_type = .enum_literal_type }); + + const undefined_value = try ip.get(gpa, .{ .simple_value = .undefined_value }); + const void_value = try ip.get(gpa, .{ .simple_value = .void_value }); + const unreachable_value = try ip.get(gpa, .{ .simple_value = .unreachable_value }); + const null_value = try ip.get(gpa, .{ .simple_value = .null_value }); + 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 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"); +} + +test "int type" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const i32_type = try ip.get(gpa, .{ .int_type = .{ .signedness = .signed, .bits = 32 } }); + const i16_type = try ip.get(gpa, .{ .int_type = .{ .signedness = .signed, .bits = 16 } }); + const u7_type = try ip.get(gpa, .{ .int_type = .{ .signedness = .unsigned, .bits = 7 } }); + const another_i32_type = try ip.get(gpa, .{ .int_type = .{ .signedness = .signed, .bits = 32 } }); + + try expect(i32_type == another_i32_type); + try expect(i32_type != u7_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"); +} + +test "int value" { + const gpa = std.testing.allocator; + + 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 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); + + try expect(unsigned_zero_value != unsigned_one_value); + try expect(unsigned_one_value != signed_zero_value); + try expect(signed_zero_value != signed_one_value); + + try expect(signed_one_value != u64_max_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 testExpectFmtValue(ip, u64_max_value, undefined, "18446744073709551615"); + try testExpectFmtValue(ip, i64_max_value, undefined, "9223372036854775807"); + try testExpectFmtValue(ip, i64_min_value, undefined, "-9223372036854775808"); +} + +test "big int value" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + var result = try std.math.big.int.Managed.init(gpa); + defer result.deinit(); + var a = try std.math.big.int.Managed.initSet(gpa, 2); + defer a.deinit(); + + 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() }); + + try testExpectFmtValue(ip, positive_big_int_value, .none, "340282366920938463463374607431768211456"); + try testExpectFmtValue(ip, negative_big_int_value, .none, "-340282366920938463463374607431768211456"); +} + +test "float type" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const f16_type = try ip.get(gpa, .{ .simple_type = .f16 }); + const f32_type = try ip.get(gpa, .{ .simple_type = .f32 }); + const f64_type = try ip.get(gpa, .{ .simple_type = .f64 }); + const f80_type = try ip.get(gpa, .{ .simple_type = .f80 }); + const f128_type = try ip.get(gpa, .{ .simple_type = .f128 }); + + const another_f32_type = try ip.get(gpa, .{ .simple_type = .f32 }); + const another_f64_type = try ip.get(gpa, .{ .simple_type = .f64 }); + + try expect(f16_type != f32_type); + try expect(f32_type != f64_type); + try expect(f64_type != f80_type); + try expect(f80_type != f128_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"); +} + +test "float value" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const f16_value = try ip.get(gpa, .{ .float_16_value = 0.25 }); + const f32_value = try ip.get(gpa, .{ .float_32_value = 0.5 }); + const f64_value = try ip.get(gpa, .{ .float_64_value = 1.0 }); + const f80_value = try ip.get(gpa, .{ .float_80_value = 2.0 }); + const f128_value = try ip.get(gpa, .{ .float_128_value = 2.75 }); + + const f32_nan_value = try ip.get(gpa, .{ .float_32_value = std.math.nan_f32 }); + const f32_qnan_value = try ip.get(gpa, .{ .float_32_value = std.math.qnan_f32 }); + + const f32_inf_value = try ip.get(gpa, .{ .float_32_value = std.math.inf_f32 }); + const f32_ninf_value = try ip.get(gpa, .{ .float_32_value = -std.math.inf_f32 }); + + const f32_zero_value = try ip.get(gpa, .{ .float_32_value = 0.0 }); + const f32_nzero_value = try ip.get(gpa, .{ .float_32_value = -0.0 }); + + try expect(f16_value != f32_value); + try expect(f32_value != f64_value); + try expect(f64_value != f80_value); + try expect(f80_value != f128_value); + + try expect(f32_nan_value != f32_qnan_value); + try expect(f32_inf_value != f32_ninf_value); + try expect(f32_zero_value != f32_nzero_value); + + try expect(!ip.indexToKey(f16_value).eql(ip.indexToKey(f32_value))); + try expect(ip.indexToKey(f32_value).eql(ip.indexToKey(f32_value))); + + try expect(ip.indexToKey(f32_nan_value).eql(ip.indexToKey(f32_nan_value))); + try expect(!ip.indexToKey(f32_nan_value).eql(ip.indexToKey(f32_qnan_value))); + + try expect(ip.indexToKey(f32_inf_value).eql(ip.indexToKey(f32_inf_value))); + try expect(!ip.indexToKey(f32_inf_value).eql(ip.indexToKey(f32_ninf_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 testExpectFmtValue(ip, f32_nan_value, undefined, "nan"); + try testExpectFmtValue(ip, f32_qnan_value, undefined, "nan"); + + try testExpectFmtValue(ip, f32_inf_value, undefined, "inf"); + try testExpectFmtValue(ip, f32_ninf_value, undefined, "-inf"); + + try testExpectFmtValue(ip, f32_zero_value, undefined, "0"); + try testExpectFmtValue(ip, f32_nzero_value, undefined, "-0"); +} + +test "pointer type" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"*i32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .i32_type, + .size = .One, + } }); + const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .size = .One, + } }); + const @"*const volatile u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .size = .One, + .is_const = true, + .is_volatile = true, + } }); + const @"*align(4:2:3) u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .size = .One, + .alignment = 4, + .bit_offset = 2, + .host_size = 3, + } }); + const @"*addrspace(.shared) const u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .size = .One, + .is_const = true, + .address_space = .shared, + } }); + + const @"[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .size = .Many, + } }); + const @"[*:0]u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .size = .Many, + .sentinel = .zero, + } }); + const @"[]u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .size = .Slice, + } }); + const @"[:0]u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .size = .Slice, + .sentinel = .zero, + } }); + const @"[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ + .elem_type = .u32_type, + .size = .C, + } }); + + try expect(@"*i32" != @"*u32"); + try expect(@"*u32" != @"*const volatile u32"); + try expect(@"*const volatile u32" != @"*align(4:2:3) u32"); + try expect(@"*align(4:2:3) u32" != @"*addrspace(.shared) const u32"); + + try expect(@"[*]u32" != @"[*:0]u32"); + try expect(@"[*:0]u32" != @"[]u32"); + 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 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"); +} + +test "optional type" { + const gpa = std.testing.allocator; + + 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 testExpectFmtValue(ip, .null_value, u32_optional_type, "null"); + try testExpectFmtValue(ip, u64_42_value, u32_optional_type, "42"); +} + +test "error set type" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const foo_name = try ip.get(gpa, .{ .bytes = "foo" }); + const bar_name = try ip.get(gpa, .{ .bytes = "bar" }); + const baz_name = try ip.get(gpa, .{ .bytes = "baz" }); + + const empty_error_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{} } }); + + const foo_bar_baz_set = try ip.get(gpa, .{ .error_set_type = .{ + .names = &.{ foo_name, bar_name, baz_name }, + } }); + + const foo_bar_set = try ip.get(gpa, .{ .error_set_type = .{ + .names = &.{ foo_name, bar_name }, + } }); + + 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}"); +} + +test "error union type" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const empty_error_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{} } }); + const bool_type = try ip.get(gpa, .{ .simple_type = .bool }); + + const @"error{}!bool" = try ip.get(gpa, .{ .error_union_type = .{ + .error_set_type = empty_error_set, + .payload_type = bool_type, + } }); + + try testExpectFmtType(ip, @"error{}!bool", "error{}!bool"); +} + +test "array type" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const i32_3_array_type = try ip.get(gpa, .{ .array_type = .{ + .len = 3, + .child = .i32_type, + } }); + const u32_0_0_array_type = try ip.get(gpa, .{ .array_type = .{ + .len = 3, + .child = .u32_type, + .sentinel = .zero, + } }); + + 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"); +} + +test "struct value" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const struct_index = try ip.createStruct(gpa, .{ + .fields = .{}, + .namespace = .none, + .layout = .Auto, + .backing_int_ty = .none, + .status = .none, + }); + 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, "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 = &.{ one_value, true_value } }); + + try ip.testExpectFmtValue(aggregate_value, struct_type, ".{.foo = 1, .bar = true}"); +} + +test "function type" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"fn(i32) bool" = try ip.get(gpa, .{ .function_type = .{ + .args = &.{.i32_type}, + .return_type = .bool_type, + } }); + + var args_is_comptime = std.StaticBitSet(32).initEmpty(); + args_is_comptime.set(0); + var args_is_noalias = std.StaticBitSet(32).initEmpty(); + args_is_noalias.set(1); + + const @"fn(comptime type, noalias i32) type" = try ip.get(gpa, .{ .function_type = .{ + .args = &.{ .type_type, .i32_type }, + .args_is_comptime = args_is_comptime, + .args_is_noalias = args_is_noalias, + .return_type = .type_type, + } }); + + const @"fn(i32, ...) type" = try ip.get(gpa, .{ .function_type = .{ + .args = &.{.i32_type}, + .return_type = .type_type, + .is_var_args = true, + } }); + + const @"fn() align(4) callconv(.C) type" = try ip.get(gpa, .{ .function_type = .{ + .args = &.{}, + .return_type = .type_type, + .alignment = 4, + .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"); +} + +test "union value" { + const gpa = std.testing.allocator; + + 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, .{ + .tag_type = .none, + .fields = .{}, + .namespace = .none, + .layout = .Auto, + .status = .none, + }); + 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, "float", .{ .ty = .f16_type, .alignment = 0 }); + + const union_value1 = try ip.get(gpa, .{ .union_value = .{ + .field_index = 0, + .val = int_value, + } }); + const union_value2 = try ip.get(gpa, .{ .union_value = .{ + .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 }"); +} + +test "anyframe type" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"anyframe->i32" = try ip.get(gpa, .{ .anyframe_type = .{ .child = .i32_type } }); + const @"anyframe->bool" = try ip.get(gpa, .{ .anyframe_type = .{ .child = .bool_type } }); + + try expect(@"anyframe->i32" != @"anyframe->bool"); + + try testExpectFmtType(ip, @"anyframe->i32", "anyframe->i32"); + try testExpectFmtType(ip, @"anyframe->bool", "anyframe->bool"); +} + +test "vector type" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"@Vector(2,u32)" = try ip.get(gpa, .{ .vector_type = .{ + .len = 2, + .child = .u32_type, + } }); + const @"@Vector(2,bool)" = try ip.get(gpa, .{ .vector_type = .{ + .len = 2, + .child = .bool_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)"); +} + +test "bytes value" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + var str1: [43]u8 = "https://www.youtube.com/watch?v=dQw4w9WgXcQ".*; + const bytes_value1 = try ip.get(gpa, .{ .bytes = &str1 }); + @memset(&str1, 0, str1.len); + + var str2: [43]u8 = "https://www.youtube.com/watch?v=dQw4w9WgXcQ".*; + const bytes_value2 = try ip.get(gpa, .{ .bytes = &str2 }); + @memset(&str2, 0, str2.len); + + var str3: [26]u8 = "https://www.duckduckgo.com".*; + const bytes_value3 = try ip.get(gpa, .{ .bytes = &str3 }); + @memset(&str3, 0, str3.len); + + try expect(bytes_value1 == bytes_value2); + try expect(bytes_value2 != bytes_value3); + + try expect(@ptrToInt(&str1) != @ptrToInt(ip.indexToKey(bytes_value1).bytes.ptr)); + try expect(@ptrToInt(&str2) != @ptrToInt(ip.indexToKey(bytes_value2).bytes.ptr)); + try expect(@ptrToInt(&str3) != @ptrToInt(ip.indexToKey(bytes_value3).bytes.ptr)); + + try std.testing.expectEqual(ip.indexToKey(bytes_value1).bytes.ptr, ip.indexToKey(bytes_value2).bytes.ptr); + + try std.testing.expectEqualStrings("https://www.youtube.com/watch?v=dQw4w9WgXcQ", ip.indexToKey(bytes_value1).bytes); + try std.testing.expectEqualStrings("https://www.youtube.com/watch?v=dQw4w9WgXcQ", ip.indexToKey(bytes_value2).bytes); + try std.testing.expectEqualStrings("https://www.duckduckgo.com", ip.indexToKey(bytes_value3).bytes); +} + +test "coerceInMemoryAllowed integers and floats" { + const gpa = std.testing.allocator; + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u32_type, .u32_type, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u32_type, .u16_type, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u16_type, .u32_type, true, builtin.target) == .int_not_coercible); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .i32_type, .u32_type, true, builtin.target) == .int_not_coercible); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u32_type, .i32_type, true, builtin.target) == .int_not_coercible); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u32_type, .i16_type, true, builtin.target) == .int_not_coercible); + + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .f32_type, .f32_type, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .f64_type, .f32_type, true, builtin.target) == .no_match); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .f32_type, .f64_type, true, builtin.target) == .no_match); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .u32_type, .f32_type, true, builtin.target) == .no_match); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .f32_type, .u32_type, true, builtin.target) == .no_match); +} + +test "coerceInMemoryAllowed error set" { + const gpa = std.testing.allocator; + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const foo_name = try ip.get(gpa, .{ .bytes = "foo" }); + const bar_name = try ip.get(gpa, .{ .bytes = "bar" }); + const baz_name = try ip.get(gpa, .{ .bytes = "baz" }); + + const foo_bar_baz_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{ baz_name, bar_name, foo_name } } }); + const foo_bar_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{ foo_name, bar_name } } }); + const foo_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{foo_name} } }); + const empty_set = try ip.get(gpa, .{ .error_set_type = .{ .names = &.{} } }); + + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, foo_bar_baz_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, foo_bar_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, foo_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, empty_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, .anyerror_type, .anyerror_type, true, builtin.target) == .ok); + + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_baz_set, .anyerror_type, true, builtin.target) == .from_anyerror); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, empty_set, .anyerror_type, true, builtin.target) == .from_anyerror); + + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_baz_set, foo_bar_baz_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_baz_set, foo_bar_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_baz_set, foo_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_baz_set, empty_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_set, foo_bar_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_set, foo_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_set, empty_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_set, foo_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_set, empty_set, true, builtin.target) == .ok); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, empty_set, empty_set, true, builtin.target) == .ok); + + try expect(try ip.coerceInMemoryAllowed(gpa, arena, empty_set, foo_set, true, builtin.target) == .missing_error); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, empty_set, foo_bar_baz_set, true, builtin.target) == .missing_error); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_set, foo_bar_set, true, builtin.target) == .missing_error); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_set, foo_bar_baz_set, true, builtin.target) == .missing_error); + try expect(try ip.coerceInMemoryAllowed(gpa, arena, foo_bar_set, foo_bar_baz_set, true, builtin.target) == .missing_error); +} + +test "resolvePeerTypes" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + try expect(.noreturn_type == try ip.resolvePeerTypes(std.testing.allocator, &.{}, builtin.target)); + try expect(.type_type == try ip.resolvePeerTypes(std.testing.allocator, &.{.type_type}, builtin.target)); + + try ip.testResolvePeerTypes(.none, .none, .none); + try ip.testResolvePeerTypes(.bool_type, .bool_type, .bool_type); + try ip.testResolvePeerTypes(.bool_type, .noreturn_type, .bool_type); + try ip.testResolvePeerTypes(.bool_type, .undefined_type, .bool_type); + try ip.testResolvePeerTypes(.type_type, .noreturn_type, .type_type); + try ip.testResolvePeerTypes(.type_type, .undefined_type, .type_type); +} + +test "resolvePeerTypes integers and floats" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + try ip.testResolvePeerTypes(.i16_type, .i16_type, .i16_type); + try ip.testResolvePeerTypes(.i16_type, .i32_type, .i32_type); + try ip.testResolvePeerTypes(.i32_type, .i64_type, .i64_type); + + try ip.testResolvePeerTypes(.u16_type, .u16_type, .u16_type); + try ip.testResolvePeerTypes(.u16_type, .u32_type, .u32_type); + try ip.testResolvePeerTypes(.u32_type, .u64_type, .u64_type); + + try ip.testResolvePeerTypesInOrder(.i16_type, .u16_type, .i16_type); + try ip.testResolvePeerTypesInOrder(.u16_type, .i16_type, .u16_type); + try ip.testResolvePeerTypesInOrder(.i32_type, .u32_type, .i32_type); + try ip.testResolvePeerTypesInOrder(.u32_type, .i32_type, .u32_type); + try ip.testResolvePeerTypesInOrder(.isize_type, .usize_type, .isize_type); + try ip.testResolvePeerTypesInOrder(.usize_type, .isize_type, .usize_type); + + try ip.testResolvePeerTypes(.i16_type, .u32_type, .u32_type); + try ip.testResolvePeerTypes(.u16_type, .i32_type, .i32_type); + try ip.testResolvePeerTypes(.i32_type, .u64_type, .u64_type); + try ip.testResolvePeerTypes(.u32_type, .i64_type, .i64_type); + + try ip.testResolvePeerTypes(.i16_type, .usize_type, .usize_type); + try ip.testResolvePeerTypes(.i16_type, .isize_type, .isize_type); + try ip.testResolvePeerTypes(.u16_type, .usize_type, .usize_type); + try ip.testResolvePeerTypes(.u16_type, .isize_type, .isize_type); + + try ip.testResolvePeerTypes(.c_short_type, .usize_type, .usize_type); + try ip.testResolvePeerTypes(.c_short_type, .isize_type, .isize_type); + + try ip.testResolvePeerTypes(.i16_type, .c_long_type, .c_long_type); + try ip.testResolvePeerTypes(.i16_type, .c_long_type, .c_long_type); + try ip.testResolvePeerTypes(.u16_type, .c_long_type, .c_long_type); + try ip.testResolvePeerTypes(.u16_type, .c_long_type, .c_long_type); + + try ip.testResolvePeerTypes(.comptime_int_type, .i16_type, .i16_type); + try ip.testResolvePeerTypes(.comptime_int_type, .u64_type, .u64_type); + try ip.testResolvePeerTypes(.comptime_int_type, .isize_type, .isize_type); + try ip.testResolvePeerTypes(.comptime_int_type, .usize_type, .usize_type); + try ip.testResolvePeerTypes(.comptime_int_type, .c_short_type, .c_short_type); + try ip.testResolvePeerTypes(.comptime_int_type, .c_int_type, .c_int_type); + try ip.testResolvePeerTypes(.comptime_int_type, .c_long_type, .c_long_type); + + try ip.testResolvePeerTypes(.comptime_float_type, .i16_type, .none); + try ip.testResolvePeerTypes(.comptime_float_type, .u64_type, .none); + try ip.testResolvePeerTypes(.comptime_float_type, .isize_type, .none); + try ip.testResolvePeerTypes(.comptime_float_type, .usize_type, .none); + try ip.testResolvePeerTypes(.comptime_float_type, .c_short_type, .none); + try ip.testResolvePeerTypes(.comptime_float_type, .c_int_type, .none); + try ip.testResolvePeerTypes(.comptime_float_type, .c_long_type, .none); + + try ip.testResolvePeerTypes(.comptime_float_type, .comptime_int_type, .comptime_float_type); + + try ip.testResolvePeerTypes(.f16_type, .f32_type, .f32_type); + try ip.testResolvePeerTypes(.f32_type, .f64_type, .f64_type); + + try ip.testResolvePeerTypes(.comptime_int_type, .f16_type, .f16_type); + try ip.testResolvePeerTypes(.comptime_int_type, .f32_type, .f32_type); + try ip.testResolvePeerTypes(.comptime_int_type, .f64_type, .f64_type); + + try ip.testResolvePeerTypes(.comptime_float_type, .f16_type, .f16_type); + try ip.testResolvePeerTypes(.comptime_float_type, .f32_type, .f32_type); + try ip.testResolvePeerTypes(.comptime_float_type, .f64_type, .f64_type); + + try ip.testResolvePeerTypes(.f16_type, .i16_type, .none); + try ip.testResolvePeerTypes(.f32_type, .u64_type, .none); + try ip.testResolvePeerTypes(.f64_type, .isize_type, .none); + try ip.testResolvePeerTypes(.f16_type, .usize_type, .none); + try ip.testResolvePeerTypes(.f32_type, .c_short_type, .none); + try ip.testResolvePeerTypes(.f64_type, .c_int_type, .none); + try ip.testResolvePeerTypes(.f64_type, .c_long_type, .none); + + try ip.testResolvePeerTypes(.bool_type, .i16_type, .none); + try ip.testResolvePeerTypes(.bool_type, .u64_type, .none); + try ip.testResolvePeerTypes(.bool_type, .usize_type, .none); + try ip.testResolvePeerTypes(.bool_type, .c_int_type, .none); + try ip.testResolvePeerTypes(.bool_type, .comptime_int_type, .none); + try ip.testResolvePeerTypes(.bool_type, .comptime_float_type, .none); + try ip.testResolvePeerTypes(.bool_type, .f32_type, .none); +} + +test "resolvePeerTypes optionals" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"?u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .u32_type } }); + const @"?bool" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = .bool_type } }); + + try ip.testResolvePeerTypes(.u32_type, .null_type, @"?u32"); + try ip.testResolvePeerTypes(.bool_type, .null_type, @"?bool"); +} + +test "resolvePeerTypes pointers" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One } }); + const @"[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Many } }); + const @"[]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Slice } }); + const @"[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .C } }); + + const @"?*u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*u32" } }); + const @"?[*]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"[*]u32" } }); + const @"?[]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"[]u32" } }); + + const @"**u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"*u32", .size = .One } }); + const @"*[*]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[*]u32", .size = .One } }); + const @"*[]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[]u32", .size = .One } }); + const @"*[*c]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[*c]u32", .size = .One } }); + + const @"?*[*]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[*]u32" } }); + const @"?*[]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[]u32" } }); + + const @"[1]u32" = try ip.get(gpa, .{ .array_type = .{ .len = 1, .child = .u32_type, .sentinel = .none } }); + const @"[2]u32" = try ip.get(gpa, .{ .array_type = .{ .len = 2, .child = .u32_type, .sentinel = .none } }); + + const @"*[1]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[1]u32", .size = .One } }); + const @"*[2]u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = @"[2]u32", .size = .One } }); + + const @"?*[1]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[1]u32" } }); + const @"?*[2]u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*[2]u32" } }); + + const @"*const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One, .is_const = true } }); + const @"[*]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Many, .is_const = true } }); + const @"[]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .Slice, .is_const = true } }); + const @"[*c]const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .C, .is_const = true } }); + + const @"?*const u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"*const u32" } }); + const @"?[*]const u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"[*]const u32" } }); + const @"?[]const u32" = try ip.get(gpa, .{ .optional_type = .{ .payload_type = @"[]const u32" } }); + + _ = @"**u32"; + _ = @"*[*c]u32"; + _ = @"?*[]u32"; + _ = @"?*[2]u32"; + _ = @"?[*]const u32"; + _ = @"?[]const u32"; + + // gain const + try ip.testResolvePeerTypes(@"*u32", @"*u32", @"*u32"); + try ip.testResolvePeerTypes(@"*u32", @"*const u32", @"*const u32"); + try ip.testResolvePeerTypes(@"[*]u32", @"[*]const u32", @"[*]const u32"); + try ip.testResolvePeerTypes(@"[]u32", @"[]const u32", @"[]const u32"); + try ip.testResolvePeerTypes(@"[*c]u32", @"[*c]const u32", @"[*c]const u32"); + + // array to slice + try ip.testResolvePeerTypes(@"*[1]u32", @"*[2]u32", @"[]u32"); + try ip.testResolvePeerTypes(@"[]u32", @"*[1]u32", @"[]u32"); + + // pointer like optionals + try ip.testResolvePeerTypes(@"*u32", @"?*u32", @"?*u32"); + try ip.testResolvePeerTypesInOrder(@"*u32", @"?[*]u32", @"?[*]u32"); + try ip.testResolvePeerTypesInOrder(@"[*]u32", @"?*u32", @"?*u32"); + + try ip.testResolvePeerTypes(@"[*c]u32", .comptime_int_type, @"[*c]u32"); + try ip.testResolvePeerTypes(@"[*c]u32", .u32_type, @"[*c]u32"); + try ip.testResolvePeerTypes(@"[*c]u32", .comptime_float_type, .none); + try ip.testResolvePeerTypes(@"[*c]u32", .bool_type, .none); + + try ip.testResolvePeerTypes(@"[*c]u32", @"*u32", @"[*c]u32"); + try ip.testResolvePeerTypes(@"[*c]u32", @"[*]u32", @"[*c]u32"); + try ip.testResolvePeerTypes(@"[*c]u32", @"[]u32", @"[*c]u32"); + + try ip.testResolvePeerTypes(@"[*c]u32", @"*[1]u32", .none); + try ip.testResolvePeerTypesInOrder(@"[*c]u32", @"?*[1]u32", @"?*[1]u32"); + try ip.testResolvePeerTypesInOrder(@"?*[1]u32", @"[*c]u32", .none); + try ip.testResolvePeerTypes(@"[*c]u32", @"*[*]u32", .none); + try ip.testResolvePeerTypesInOrder(@"[*c]u32", @"?*[*]u32", @"?*[*]u32"); + try ip.testResolvePeerTypesInOrder(@"?*[*]u32", @"[*c]u32", .none); + try ip.testResolvePeerTypes(@"[*c]u32", @"[]u32", @"[*c]u32"); + // TODO try ip.testResolvePeerTypesInOrder(@"[*c]u32", @"?[]u32", @"?[]u32"); + // TODO try ip.testResolvePeerTypesInOrder(@"?[]u32", @"[*c]u32", Index.none); + + // TODO try ip.testResolvePeerTypesInOrder(@"*u32", @"?[*]const u32", @"?[*]const u32"); + try ip.testResolvePeerTypesInOrder(@"*const u32", @"?[*]u32", @"?[*]u32"); + try ip.testResolvePeerTypesInOrder(@"[*]const u32", @"?*u32", @"?*u32"); + try ip.testResolvePeerTypesInOrder(@"[*]u32", @"?*const u32", @"?*const u32"); + + try ip.testResolvePeerTypes(@"?[*]u32", @"*[2]u32", @"?[*]u32"); + try ip.testResolvePeerTypes(@"?[]u32", @"*[2]u32", @"?[]u32"); + try ip.testResolvePeerTypes(@"[*]u32", @"*[2]u32", @"[*]u32"); +} + +test "resolvePeerTypes function pointers" { + const gpa = std.testing.allocator; + + var ip = try InternPool.init(gpa); + defer ip.deinit(gpa); + + const @"*u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One } }); + const @"*const u32" = try ip.get(gpa, .{ .pointer_type = .{ .elem_type = .u32_type, .size = .One, .is_const = true } }); + + const @"fn(*u32) void" = try ip.get(gpa, .{ .function_type = .{ + .args = &.{@"*u32"}, + .return_type = .void_type, + } }); + + const @"fn(*const u32) void" = try ip.get(gpa, .{ .function_type = .{ + .args = &.{@"*const u32"}, + .return_type = .void_type, + } }); + + try ip.testResolvePeerTypes(@"fn(*u32) void", @"fn(*u32) void", @"fn(*u32) void"); + try ip.testResolvePeerTypes(@"fn(*u32) void", @"fn(*const u32) void", @"fn(*u32) void"); +} + +fn testResolvePeerTypes(ip: *InternPool, a: Index, b: Index, expected: Index) !void { + try ip.testResolvePeerTypesInOrder(a, b, expected); + try ip.testResolvePeerTypesInOrder(b, a, expected); +} + +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); +} + +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.*)}); + 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.*)}); + defer if (actual != .none) allocator.free(actual_type); + + std.debug.print("expected `{s}`, found `{s}`\n", .{ expected_type, actual_type }); + return error.TestExpectedEqual; +} diff --git a/src/analyser/analyser.zig b/src/analyser/analyser.zig new file mode 100644 index 0000000..94af823 --- /dev/null +++ b/src/analyser/analyser.zig @@ -0,0 +1,8 @@ +pub const completions = @import("completions.zig"); +pub const InternPool = @import("InternPool.zig"); +pub const encoding = @import("encoding.zig"); + +comptime { + const std = @import("std"); + std.testing.refAllDecls(@This()); +} diff --git a/src/analyser/completions.zig b/src/analyser/completions.zig new file mode 100644 index 0000000..bddeeeb --- /dev/null +++ b/src/analyser/completions.zig @@ -0,0 +1,209 @@ +const std = @import("std"); +const InternPool = @import("InternPool.zig"); +const types = @import("../lsp.zig"); + +const Ast = std.zig.Ast; + +pub fn dotCompletions( + arena: std.mem.Allocator, + completions: *std.ArrayListUnmanaged(types.CompletionItem), + ip: *InternPool, + ty: InternPool.Index, + val: InternPool.Index, + node: ?Ast.Node.Index, +) error{OutOfMemory}!void { + _ = 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, + }; + + switch (inner_key) { + .simple_type => |simple| switch (simple) { + .type => { + const ty_key = ip.indexToKey(val); + const namespace = ty_key.getNamespace(ip.*); + if (namespace != .none) { + // TODO lookup in namespace + } + switch (ty_key) { + .error_set_type => |error_set_info| { + for (error_set_info.names) |name| { + const error_name = ip.indexToKey(name).bytes; + try completions.append(arena, .{ + .label = error_name, + .kind = .Constant, + .detail = try std.fmt.allocPrint(arena, "error.{s}", .{std.zig.fmtId(error_name)}), + }); + } + }, + .union_type => {}, // TODO + .enum_type => |enum_index| { + const enum_info = ip.getEnum(enum_index); + var field_it = enum_info.fields.iterator(); + while (field_it.next()) |entry| { + try completions.append(arena, .{ + .label = entry.key_ptr.*, + .kind = .Constant, + // include field.val? + }); + } + }, + else => {}, + } + }, + else => {}, + }, + .pointer_type => |pointer_info| { + if (pointer_info.size == .Slice) { + var many_ptr_info = InternPool.Key{ .pointer_type = pointer_info }; + many_ptr_info.pointer_type.size = .Many; + + try completions.append(arena, .{ + .label = "ptr", + .kind = .Field, + .detail = try std.fmt.allocPrint(arena, "ptr: {}", .{many_ptr_info.fmtType(ip.*)}), + }); + try completions.append(arena, .{ + .label = "len", + .kind = .Field, + .detail = "len: usize", + }); + } else if (ip.indexToKey(pointer_info.elem_type) == .array_type) { + try completions.append(arena, .{ + .label = "len", + .kind = .Field, + .detail = "len: usize", + }); + } + }, + .array_type => |array_info| { + try completions.append(arena, types.CompletionItem{ + .label = "len", + .kind = .Field, + .detail = try std.fmt.allocPrint(arena, "const len: usize ({d})", .{array_info.len}), // TODO how should this be displayed + }); + }, + .struct_type => |struct_index| { + const struct_info = ip.getStruct(struct_index); + try completions.ensureUnusedCapacity(arena, struct_info.fields.count()); + var field_it = struct_info.fields.iterator(); + while (field_it.next()) |entry| { + const label = entry.key_ptr.*; + const field = entry.value_ptr.*; + completions.appendAssumeCapacity(types.CompletionItem{ + .label = label, + .kind = .Field, + .detail = try std.fmt.allocPrint(arena, "{s}: {}", .{ + label, + fmtFieldDetail(field, ip), + }), + }); + } + }, + .optional_type => |optional_info| { + try completions.append(arena, .{ + .label = "?", + .kind = .Operator, + .detail = try std.fmt.allocPrint(arena, "{}", .{optional_info.payload_type.fmtType(ip.*)}), + }); + }, + .enum_type => |enum_index| { + const enum_info = ip.getEnum(enum_index); + for (enum_info.fields.keys()) |field_name, i| { + const field_val = enum_info.values.keys()[i]; + try completions.append(arena, .{ + .label = field_name, + .kind = .Field, + .detail = try std.fmt.allocPrint(arena, "{}", .{field_val.fmtValue(enum_info.tag_type, ip.*)}), + }); + } + }, + .union_type => |union_index| { + const union_info = ip.getUnion(union_index); + var field_it = union_info.fields.iterator(); + while (field_it.next()) |entry| { + const label = entry.key_ptr.*; + const field = entry.value_ptr.*; + try completions.append(arena, .{ + .label = label, + .kind = .Field, + .detail = if (field.alignment != 0) + try std.fmt.allocPrint(arena, "{s}: align({d}) {}", .{ label, field.alignment, field.ty.fmtType(ip.*) }) + else + try std.fmt.allocPrint(arena, "{s}: {}", .{ label, field.ty.fmtType(ip.*) }), + }); + } + }, + .tuple_type => |tuple_info| { + for (tuple_info.types) |tuple_ty, i| { + 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.*) }), + }); + } + }, + .int_type, + .error_set_type, + .error_union_type, + .function_type, + .vector_type, + .anyframe_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, + => unreachable, + + .bytes, + .aggregate, + .union_value, + => unreachable, + } +} + +fn FormatContext(comptime T: type) type { + return struct { + ip: *InternPool, + item: T, + }; +} + +fn formatFieldDetail( + ctx: FormatContext(InternPool.Struct.Field), + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + _ = options; + if (fmt.len != 0) std.fmt.invalidFmtError(fmt, InternPool.Struct.Field); + + const field = ctx.item; + if (field.is_comptime) { + try writer.writeAll("comptime "); + } + if (field.alignment != 0) { + try writer.print("align({d}) ", .{field.alignment}); + } + try writer.print("{}", .{field.ty.fmtType(ctx.ip.*)}); + if (field.default_value != .none) { + try writer.print(" = {},", .{field.default_value.fmtValue(field.ty, ctx.ip.*)}); + } +} + +pub fn fmtFieldDetail(field: InternPool.Struct.Field, ip: *InternPool) std.fmt.Formatter(formatFieldDetail) { + return .{ .data = .{ + .ip = ip, + .item = field, + } }; +} diff --git a/src/analyser/encoding.zig b/src/analyser/encoding.zig new file mode 100644 index 0000000..79f9709 --- /dev/null +++ b/src/analyser/encoding.zig @@ -0,0 +1,210 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +const Index = usize; + +pub fn encode(extra: *std.ArrayList(u8), comptime T: type, data: anytype) Allocator.Error!void { + switch (@typeInfo(T)) { + .Type, + .NoReturn, + .ComptimeFloat, + .ComptimeInt, + .Undefined, + .Null, + .ErrorUnion, + .ErrorSet, + .Fn, + .Opaque, + .Frame, + .AnyFrame, + .EnumLiteral, + => @compileError("Unable to encode type " ++ @typeName(T)), + + .Void => {}, + .Bool => try encode(extra, u1, @boolToInt(data)), + .Int => try extra.appendSlice(std.mem.asBytes(&data)), + .Float => |info| switch (info.bits) { + 16 => try encode(extra, u16, @bitCast(u16, data)), + 32 => try encode(extra, u32, @bitCast(u32, data)), + 64 => try encode(extra, u64, @bitCast(u64, data)), + 80 => try encode(extra, u80, @bitCast(u80, data)), + 128 => try encode(extra, u128, @bitCast(u128, data)), + else => @compileError("Unable to encode type " ++ @typeName(T)), + }, + .Pointer => |info| { + switch (info.size) { + .One => { + if (comptime canEncodeAsBytes(info.child)) { + try extra.appendNTimes(undefined, std.mem.alignPointerOffset(extra.items.ptr + extra.items.len, info.alignment).?); + try encode(extra, info.child, data.*); + } else { + @compileError("Encoding " ++ @typeName(T) ++ " would require allocation"); + } + }, + .Slice => { + if (comptime canEncodeAsBytes(info.child)) { + try encode(extra, u32, @intCast(u32, data.len)); + try extra.appendNTimes(undefined, std.mem.alignPointerOffset(extra.items.ptr + extra.items.len, info.alignment).?); + try extra.appendSlice(std.mem.sliceAsBytes(data)); + } else { + @compileError("Encoding " ++ @typeName(T) ++ " would require allocation"); + } + }, + + .Many, + .C, + => @compileError("Unable to encode type " ++ @typeName(T)), + } + }, + .Array => |info| { + for (data) |item| { + try encode(extra, info.child, item); + } + }, + .Struct => |info| { + switch (info.layout) { + .Packed, + .Extern, + => return try extra.appendSlice(std.mem.asBytes(&data)), + .Auto => { + inline for (info.fields) |field| { + try encode(extra, field.type, @field(data, field.name)); + } + }, + } + }, + .Optional => { + try encode(extra, bool, data == null); + if (data) |item| { + try encode(extra, item); + } + }, + .Enum => |info| try encode(extra, info.tag_type, @enumToInt(data)), + .Union => @compileError("TODO"), + .Vector => |info| { + const array: [info.len]info.child = data; + try encode(extra, array); + }, + } +} + +pub fn decode(extra: *[]const u8, comptime T: type) T { + return switch (@typeInfo(T)) { + .Type, + .NoReturn, + .ComptimeFloat, + .ComptimeInt, + .Undefined, + .Null, + .ErrorUnion, + .ErrorSet, + .Fn, + .Opaque, + .Frame, + .AnyFrame, + .EnumLiteral, + => @compileError("Unable to decode type " ++ @typeName(T)), + + .Void => {}, + .Bool => decode(extra, u1) == 1, + .Int => std.mem.bytesToValue(T, readArray(extra, @sizeOf(T))), + .Float => |info| switch (info.bits) { + 16 => @bitCast(T, decode(extra, u16)), + 32 => @bitCast(T, decode(extra, u32)), + 64 => @bitCast(T, decode(extra, u64)), + 80 => @bitCast(T, decode(extra, u80)), + 128 => @bitCast(T, decode(extra, u128)), + else => @compileError("Unable to decode type " ++ @typeName(T)), + }, + .Pointer => |info| { + switch (info.size) { + .One => { + if (comptime canEncodeAsBytes(info.child)) { + extra.* = alignForward(extra.*, info.alignment); + return std.mem.bytesAsValue(T, readArray(extra, @sizeOf(info.child))); + } else { + @compileError("Decoding " ++ @typeName(T) ++ " would require allocation"); + } + }, + .Slice => { + if (comptime canEncodeAsBytes(info.child)) { + const len = decode(extra, u32); + extra.* = alignForward(extra.*, info.alignment); + const bytes = readBytes(extra, len * @sizeOf(info.child)); + return std.mem.bytesAsSlice(info.child, @alignCast(info.alignment, bytes)); + } else { + @compileError("Decoding " ++ @typeName(T) ++ " would require allocation"); + } + }, + + .Many, + .C, + => @compileError("Unable to decode type " ++ @typeName(T)), + } + }, + .Array => |info| blk: { + var array: T = undefined; + var i: usize = 0; + while (i < info.len) { + array[i] = decode(extra, info.child); + } + break :blk array; + }, + .Struct => |info| { + switch (info.layout) { + .Packed, + .Extern, + => return std.mem.bytesToValue(T, readArray(extra, @sizeOf(T))), + .Auto => { + var result: T = undefined; + inline for (info.fields) |field| { + @field(result, field.name) = decode(extra, field.type); + } + return result; + }, + } + }, + .Optional => |info| blk: { + const is_null = decode(extra, bool); + if (is_null) { + break :blk null; + } else { + break :blk decode(extra, info.child); + } + }, + .Enum => |info| @intToEnum(T, decode(extra, info.tag_type)), + .Union => @compileError("TODO"), + .Vector => |info| decode(extra, [info.len]info.child), + }; +} + +pub fn canEncodeAsBytes(comptime T: type) bool { + return switch (@typeInfo(T)) { + .Void, .Bool, .Int, .Float, .Enum, .Vector => true, + .Array => |info| canEncodeAsBytes(info.child), + .Struct => |info| info.layout != .Auto, + .Union => |info| info.layout != .Auto, + else => false, + }; +} + +/// forward aligns `extra` until it has the given alignment +pub fn alignForward(extra: []const u8, alignment: usize) []const u8 { + const unaligned = @ptrToInt(extra.ptr); + const offset = std.mem.alignForward(unaligned, alignment) - unaligned; + const result = extra[offset..]; + std.debug.assert(std.mem.isAligned(@ptrToInt(result.ptr), alignment)); + return result; +} + +pub fn readBytes(extra: *[]const u8, n: usize) []const u8 { + defer extra.* = extra.*[n..]; + return extra.*[0..n]; +} + +pub fn readArray(extra: *[]const u8, comptime n: usize) *const [n]u8 { + defer extra.* = extra.*[n..]; + return extra.*[0..n]; +} diff --git a/src/analysis.zig b/src/analysis.zig index b1491b0..25bbc28 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -7,6 +7,7 @@ const URI = @import("uri.zig"); const log = std.log.scoped(.analysis); const ast = @import("ast.zig"); const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); +const InternPool = ComptimeInterpreter.InternPool; var using_trail: std.ArrayList([*]const u8) = undefined; var resolve_trail: std.ArrayList(NodeWithHandle) = undefined; @@ -762,41 +763,39 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, node_handle: NodeWithHan log.info("Invoking interpreter!", .{}); - store.ensureInterpreterExists(handle.uri) catch |err| { - log.err("Interpreter error: {s}", .{@errorName(err)}); + const interpreter = store.ensureInterpreterExists(handle.uri) catch |err| { + log.err("Failed to interpret file: {s}", .{@errorName(err)}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } return null; }; - var interpreter = handle.interpreter.?; + + const root_namespace = @intToEnum(ComptimeInterpreter.Namespace.Index, 0); // TODO: Start from current/nearest-current scope - const result = interpreter.interpret(node, interpreter.root_type.?.getTypeInfo().getScopeOfType().?, .{}) catch |err| { - log.err("Interpreter error: {s}", .{@errorName(err)}); + const result = interpreter.interpret(node, root_namespace, .{}) catch |err| { + log.err("Failed to interpret node: {s}", .{@errorName(err)}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } return null; }; - const val = result.getValue() catch |err| { - log.err("Interpreter error: {s}", .{@errorName(err)}); + const value = result.getValue() catch |err| { + log.err("interpreter return no result: {s}", .{@errorName(err)}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } return null; }; - const ti = val.type.getTypeInfo(); - if (ti != .type) { - log.err("Not a type: { }", .{interpreter.formatTypeInfo(ti)}); - return null; - } - return TypeWithHandle{ .type = .{ - .data = .{ .@"comptime" = .{ .interpreter = interpreter, .type = val.value_data.type } }, - .is_type_val = true, + .data = .{ .@"comptime" = .{ + .interpreter = interpreter, + .value = value, + } }, + .is_type_val = value.ty == InternPool.Index.type_type, }, .handle = node_handle.handle, }; @@ -1050,7 +1049,7 @@ pub const Type = struct { array_index, @"comptime": struct { interpreter: *ComptimeInterpreter, - type: ComptimeInterpreter.Type, + value: ComptimeInterpreter.Value, }, }, /// If true, the type `type`, the attached data is the value of the type value. diff --git a/src/zls.zig b/src/zls.zig index 2779184..77ca108 100644 --- a/src/zls.zig +++ b/src/zls.zig @@ -14,3 +14,9 @@ pub const URI = @import("uri.zig"); pub const DocumentStore = @import("DocumentStore.zig"); pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); pub const diff = @import("diff.zig"); +pub const analyser = @import("analyser/analyser.zig"); + +comptime { + const std = @import("std"); + std.testing.refAllDecls(@This()); +} diff --git a/tests/language_features/comptime_interpreter.zig b/tests/language_features/comptime_interpreter.zig index cc42b3d..4f7c9c4 100644 --- a/tests/language_features/comptime_interpreter.zig +++ b/tests/language_features/comptime_interpreter.zig @@ -1,96 +1,482 @@ const std = @import("std"); const zls = @import("zls"); +const builtin = @import("builtin"); const Ast = std.zig.Ast; const ComptimeInterpreter = zls.ComptimeInterpreter; +const InternPool = zls.analyser.InternPool; +const Index = InternPool.Index; +const Key = InternPool.Key; +const ast = zls.ast; +const offsets = zls.offsets; const allocator: std.mem.Allocator = std.testing.allocator; -test "ComptimeInterpreter - basic test" { - var config = zls.Config{}; - var doc_store = zls.DocumentStore{ - .allocator = allocator, - .config = &config, - }; - defer doc_store.deinit(); +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 doc_store.openDocument("file:///file.zig", - \\pub fn ReturnMyType(comptime my_arg: bool) type { - \\ var abc = z: {break :z if (!my_arg) 123 else 0;}; - \\ if (abc == 123) return u69; - \\ return u8; - \\} - ); - - var interpreter = ComptimeInterpreter{ - .allocator = allocator, - .document_store = &doc_store, - .uri = "file:///file.zig", - }; - defer interpreter.deinit(); - - _ = try interpreter.interpret(0, null, .{}); - - var bool_type = try interpreter.createType(std.math.maxInt(std.zig.Ast.Node.Index), .{ .bool = {} }); - var arg_false = ComptimeInterpreter.Value{ - .interpreter = &interpreter, - .node_idx = std.math.maxInt(std.zig.Ast.Node.Index), - .type = bool_type, - .value_data = try interpreter.createValueData(.{ .bool = false }), - }; - var arg_true = ComptimeInterpreter.Value{ - .interpreter = &interpreter, - .node_idx = std.math.maxInt(std.zig.Ast.Node.Index), - .type = bool_type, - .value_data = try interpreter.createValueData(.{ .bool = true }), - }; - - const rmt = interpreter.root_type.?.getTypeInfo().@"struct".scope.declarations.get("ReturnMyType").?; - - const call_with_false = try interpreter.call(null, rmt.node_idx, &.{ - arg_false, - }, .{}); - defer call_with_false.scope.deinit(); - const call_with_true = try interpreter.call(null, rmt.node_idx, &.{ - arg_true, - }, .{}); - defer call_with_true.scope.deinit(); - - try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(call_with_false.result.value.value_data.type.getTypeInfo())}); - try std.testing.expectFmt("u8", "{any}", .{interpreter.formatTypeInfo(call_with_true.result.value.value_data.type.getTypeInfo())}); + 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 }); } -test "ComptimeInterpreter - struct" { - var config = zls.Config{}; - var doc_store = zls.DocumentStore{ - .allocator = allocator, - .config = &config, - }; - defer doc_store.deinit(); +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 doc_store.openDocument("file:///file.zig", - \\pub fn ReturnMyType() type { + 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("@as(?bool, null) orelse true", .{ .simple_type = .bool }, .{ .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 }); +} + +test "ComptimeInterpreter - string literal" { + var context = try Context.init( + \\const foobarbaz = "hello world!"; + \\ + ); + defer context.deinit(); + const result = try context.interpret(context.findVar("foobarbaz")); + + try std.testing.expect(result.ty == .pointer_type); + + try std.testing.expectEqualStrings("hello world!", result.val.?.bytes); +} + +test "ComptimeInterpreter - labeled block" { + try testExpr( + \\blk: { + \\ break :blk true; + \\} + , .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); + try testExpr( + \\blk: { + \\ break :blk 3; + \\} + , .{ .simple_type = .comptime_int }, .{ .int_u64_value = 3 }); +} + +test "ComptimeInterpreter - if" { + try testExpr( + \\blk: { + \\ break :blk if (true) true else false; + \\} + , .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); + try testExpr( + \\blk: { + \\ break :blk if (false) true else false; + \\} + , .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); + try testExpr( + \\blk: { + \\ if (false) break :blk true; + \\ break :blk false; + \\} + , .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); + // TODO + // try testExpr( + // \\outer: { + // \\ if (:inner { + // \\ break :inner true; + // \\ }) break :outer true; + // \\ break :outer false; + // \\} + // , .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); +} + +test "ComptimeInterpreter - variable lookup" { + try testExpr( + \\blk: { + \\ var foo = 42; + \\ break :blk foo; + \\} + , .{ .simple_type = .comptime_int }, .{ .int_u64_value = 42 }); + try testExpr( + \\blk: { + \\ var foo = 1; + \\ var bar = 2; + \\ var baz = 3; + \\ break :blk bar; + \\} + , .{ .simple_type = .comptime_int }, .{ .int_u64_value = 2 }); + + var context = try Context.init( + \\const bar = foo; + \\const foo = 3; + ); + 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); +} + +test "ComptimeInterpreter - field access" { + try testExpr( + \\blk: { + \\ const foo: struct {alpha: u64, beta: bool} = undefined; + \\ break :blk foo.beta; + \\} + , .{ .simple_type = .bool }, null); + try testExpr( + \\blk: { + \\ const foo: struct {alpha: u64, beta: bool} = undefined; + \\ break :blk foo.alpha; + \\} + , .{ .int_type = .{ + .signedness = .unsigned, + .bits = 64, + } }, null); +} + +test "ComptimeInterpreter - optional operations" { + if (true) return error.SkipZigTest; // TODO + try testExpr( + \\blk: { + \\ const foo: ?bool = true; + \\ break :blk foo.?; + \\} + , .{ .simple_type = .bool }, .{ .simple_value = .bool_true }); + try testExpr( + \\blk: { + \\ const foo: ?bool = true; + \\ break :blk foo == null; + \\} + , .{ .simple_type = .bool }, .{ .simple_value = .bool_false }); +} + +test "ComptimeInterpreter - pointer operations" { + if (true) return error.SkipZigTest; // TODO + try testExpr( + \\blk: { + \\ const foo: []const u8 = ""; + \\ break :blk foo.len; + \\} + , .{ .simple_type = .usize }, .{ .bytes = "" }); + try testExpr( + \\blk: { + \\ const foo = true; + \\ break :blk &foo; + \\} + , @panic("TODO"), .{ .simple_value = .bool_true }); + try testExpr( + \\blk: { + \\ const foo = true; + \\ const bar = &foo; + \\ break :blk bar.*; + \\} + , @panic("TODO"), .{ .simple_value = .bool_true }); +} + +test "ComptimeInterpreter - call return primitive type" { + try testCall( + \\pub fn Foo() type { + \\ return bool; + \\} + , &.{}, .{ .simple_type = .bool }); + + try testCall( + \\pub fn Foo() type { + \\ return u32; + \\} + , &.{}, .{ .int_type = .{ .signedness = .unsigned, .bits = 32 } }); + + try testCall( + \\pub fn Foo() type { + \\ return i128; + \\} + , &.{}, .{ .int_type = .{ .signedness = .signed, .bits = 128 } }); + + try testCall( + \\pub fn Foo() type { + \\ const alpha = i128; + \\ return alpha; + \\} + , &.{}, .{ .int_type = .{ .signedness = .signed, .bits = 128 } }); +} + +test "ComptimeInterpreter - call return struct" { + var context = try Context.init( + \\pub fn Foo() type { \\ return struct { \\ slay: bool, \\ var abc = 123; \\ }; \\} ); + defer context.deinit(); + const result = try context.call(context.findFn("Foo"), &.{}); - var interpreter = ComptimeInterpreter{ - .allocator = allocator, - .document_store = &doc_store, - .uri = "file:///file.zig", - }; - defer interpreter.deinit(); + try std.testing.expect(result.ty == .simple_type); + try std.testing.expect(result.ty.simple_type == .type); + const struct_info = context.interpreter.ip.getStruct(result.val.?.struct_type); + try std.testing.expectEqual(Index.none, struct_info.backing_int_ty); + try std.testing.expectEqual(std.builtin.Type.ContainerLayout.Auto, struct_info.layout); - _ = try interpreter.interpret(0, null, .{}); - - const rmt = interpreter.root_type.?.getTypeInfo().@"struct".scope.declarations.get("ReturnMyType").?; - - const z = try interpreter.call(null, rmt.node_idx, &.{}, .{}); - defer z.scope.deinit(); - - try std.testing.expectFmt("struct {slay: bool, var abc: comptime_int = 123, }", "{any}", .{interpreter.formatTypeInfo(z.result.value.value_data.type.getTypeInfo())}); + try std.testing.expectEqual(@as(usize, 1), struct_info.fields.count()); + try std.testing.expectEqualStrings("slay", struct_info.fields.keys()[0]); + try std.testing.expect(struct_info.fields.values()[0].ty == Index.bool_type); +} + +test "ComptimeInterpreter - call comptime argument" { + var context = try Context.init( + \\pub fn Foo(comptime my_arg: bool) type { + \\ var abc = z: {break :z if (!my_arg) 123 else 0;}; + \\ if (abc == 123) return u69; + \\ return u8; + \\} + ); + defer context.deinit(); + + const result1 = try context.call(context.findFn("Foo"), &.{KV{ + .ty = .{ .simple_type = .bool }, + .val = .{ .simple_value = .bool_true }, + }}); + try std.testing.expect(result1.ty == .simple_type); + try std.testing.expect(result1.ty.simple_type == .type); + try std.testing.expectEqual(Key{ .int_type = .{ .signedness = .unsigned, .bits = 8 } }, result1.val.?); + + var result2 = try context.call(context.findFn("Foo"), &.{KV{ + .ty = .{ .simple_type = .bool }, + .val = .{ .simple_value = .bool_false }, + }}); + try std.testing.expect(result2.ty == .simple_type); + try std.testing.expect(result2.ty.simple_type == .type); + try std.testing.expectEqual(Key{ .int_type = .{ .signedness = .unsigned, .bits = 69 } }, result2.val.?); +} + +test "ComptimeInterpreter - call inner function" { + try testCall( + \\pub fn Inner() type { + \\ return bool; + \\} + \\pub fn Foo() type { + \\ return Inner(); + \\} + , &.{}, .{ .simple_type = .bool }); +} + +// +// Helper functions +// + +const KV = struct { + ty: Key, + val: ?Key, +}; + +const Context = struct { + config: *zls.Config, + document_store: *zls.DocumentStore, + interpreter: *ComptimeInterpreter, + + pub fn init(source: []const u8) !Context { + var config = try allocator.create(zls.Config); + errdefer allocator.destroy(config); + + var document_store = try allocator.create(zls.DocumentStore); + errdefer allocator.destroy(document_store); + + var interpreter = try allocator.create(ComptimeInterpreter); + errdefer allocator.destroy(interpreter); + + config.* = .{}; + document_store.* = .{ + .allocator = allocator, + .config = config, + }; + errdefer document_store.deinit(); + + const test_uri: []const u8 = switch (builtin.os.tag) { + .windows => "file:///C:\\test.zig", + else => "file:///test.zig", + }; + + const handle = try document_store.openDocument(test_uri, source); + + // TODO handle handle.tree.errors + + interpreter.* = .{ + .allocator = allocator, + .ip = try InternPool.init(allocator), + .document_store = document_store, + .uri = handle.uri, + }; + errdefer interpreter.deinit(); + + _ = try interpretReportErrors(interpreter, 0, .none); + + return .{ + .config = config, + .document_store = document_store, + .interpreter = interpreter, + }; + } + + pub fn deinit(self: *Context) void { + self.interpreter.deinit(); + self.document_store.deinit(); + + allocator.destroy(self.config); + allocator.destroy(self.document_store); + allocator.destroy(self.interpreter); + } + + pub fn call(self: *Context, func_node: Ast.Node.Index, arguments: []const KV) !KV { + var args = try allocator.alloc(ComptimeInterpreter.Value, arguments.len); + defer allocator.free(args); + + for (arguments) |argument, i| { + 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, + }; + } + + 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); + + 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), + }; + } + + pub fn interpret(self: *Context, node: Ast.Node.Index) !KV { + 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); + + return KV{ + .ty = self.interpreter.ip.indexToKey(result.ty), + .val = if (result.val == .none) null else self.interpreter.ip.indexToKey(result.val), + }; + } + + pub fn findFn(self: Context, name: []const u8) Ast.Node.Index { + const handle = self.interpreter.getHandle(); + for (handle.tree.nodes.items(.tag)) |tag, i| { + if (tag != .fn_decl) continue; + const node = @intCast(Ast.Node.Index, i); + var buffer: [1]Ast.Node.Index = undefined; + const fn_decl = handle.tree.fullFnProto(&buffer, node).?; + const fn_name = offsets.tokenToSlice(handle.tree, fn_decl.name_token.?); + if (std.mem.eql(u8, fn_name, name)) return node; + } + std.debug.panic("failed to find function with name '{s}'", .{name}); + } + + pub fn findVar(self: Context, name: []const u8) Ast.Node.Index { + const handle = self.interpreter.getHandle(); + var node: Ast.Node.Index = 0; + while (node < handle.tree.nodes.len) : (node += 1) { + const var_decl = handle.tree.fullVarDecl(node) orelse continue; + const name_token = var_decl.ast.mut_token + 1; + const var_name = offsets.tokenToSlice(handle.tree, name_token); + if (std.mem.eql(u8, var_name, name)) return var_decl.ast.init_node; + } + std.debug.panic("failed to find var declaration with name '{s}'", .{name}); + } +}; + +fn testCall( + source: []const u8, + arguments: []const KV, + expected_ty: Key, +) !void { + var context = try Context.init(source); + defer context.deinit(); + + const result = try context.call(context.findFn("Foo"), arguments); + + try expectEqualKey(context.interpreter.ip, Key{ .simple_type = .type }, result.ty); + try expectEqualKey(context.interpreter.ip, expected_ty, result.val); +} + +fn testExpr( + expr: []const u8, + expected_ty: Key, + expected_val: ?Key, +) !void { + const source = try std.fmt.allocPrint(allocator, + \\const foobarbaz = {s}; + , .{expr}); + defer allocator.free(source); + + var context = try Context.init(source); + defer context.deinit(); + + 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); + } +} + +/// 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 + } + } 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}); + } + } + return error.TestExpectedEqual; +} + +fn interpretReportErrors( + interpreter: *ComptimeInterpreter, + node_idx: Ast.Node.Index, + namespace: InternPool.NamespaceIndex, +) !ComptimeInterpreter.InterpretResult { + const result = interpreter.interpret(node_idx, namespace, .{}); + + // TODO use ErrorBuilder + var err_it = interpreter.errors.iterator(); + if (interpreter.errors.count() != 0) { + const handle = interpreter.getHandle(); + std.debug.print("\n{s}\n", .{handle.text}); + while (err_it.next()) |entry| { + const token = handle.tree.firstToken(entry.key_ptr.*); + const position = offsets.tokenToPosition(handle.tree, token, .@"utf-8"); + std.debug.print("{d}:{d}: {s}\n", .{ position.line, position.character, entry.value_ptr.message }); + } + } + return result; }