Merge pull request #793 from Techatrix/intern-pool
ComptimeInterpreter: Intern Pool
This commit is contained in:
		
						commit
						c3f58538e8
					
				
							
								
								
									
										20
									
								
								build.zig
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								build.zig
									
									
									
									
									
								
							@ -178,17 +178,19 @@ pub fn build(b: *std.build.Builder) !void {
 | 
				
			|||||||
    const test_step = b.step("test", "Run all the tests");
 | 
					    const test_step = b.step("test", "Run all the tests");
 | 
				
			||||||
    test_step.dependOn(b.getInstallStep());
 | 
					    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(.{
 | 
					    var tests = b.addTest(.{
 | 
				
			||||||
        .root_source_file = .{ .path = "tests/tests.zig" },
 | 
					        .root_source_file = .{ .path = "tests/tests.zig" },
 | 
				
			||||||
        .target = target,
 | 
					        .target = target,
 | 
				
			||||||
        .optimize = .Debug,
 | 
					        .optimize = .Debug,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tests.setFilter(b.option(
 | 
					    tests.setFilter(test_filter);
 | 
				
			||||||
        []const u8,
 | 
					 | 
				
			||||||
        "test-filter",
 | 
					 | 
				
			||||||
        "Skip tests that do not match filter",
 | 
					 | 
				
			||||||
    ));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (coverage) {
 | 
					    if (coverage) {
 | 
				
			||||||
        const src_dir = b.pathJoin(&.{ build_root_path, "src" });
 | 
					        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);
 | 
					    tests.addModule("diffz", diffz_module);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test_step.dependOn(&tests.step);
 | 
					    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 {
 | 
					const CheckSubmodulesStep = struct {
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -68,6 +68,10 @@ pub const Handle = struct {
 | 
				
			|||||||
    associated_build_file: ?Uri = null,
 | 
					    associated_build_file: ?Uri = null,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn deinit(self: *Handle, allocator: std.mem.Allocator) void {
 | 
					    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.document_scope.deinit(allocator);
 | 
				
			||||||
        self.tree.deinit(allocator);
 | 
					        self.tree.deinit(allocator);
 | 
				
			||||||
        allocator.free(self.text);
 | 
					        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");
 | 
					    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).?;
 | 
					    var handle = self.handles.get(uri).?;
 | 
				
			||||||
    if (handle.interpreter == null) {
 | 
					    if (handle.interpreter != null) return handle.interpreter.?;
 | 
				
			||||||
        var int = try self.allocator.create(ComptimeInterpreter);
 | 
					
 | 
				
			||||||
        int.* = ComptimeInterpreter{
 | 
					    {
 | 
				
			||||||
 | 
					        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,
 | 
					            .allocator = self.allocator,
 | 
				
			||||||
 | 
					            .ip = ip,
 | 
				
			||||||
            .document_store = self,
 | 
					            .document_store = self,
 | 
				
			||||||
            .uri = uri,
 | 
					            .uri = uri,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        handle.interpreter = int;
 | 
					        handle.interpreter = interpreter;
 | 
				
			||||||
        _ = try int.interpret(0, null, .{});
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _ = try handle.interpreter.?.interpret(0, .none, .{});
 | 
				
			||||||
 | 
					    return handle.interpreter.?;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -21,6 +21,7 @@ const tracy = @import("tracy.zig");
 | 
				
			|||||||
const uri_utils = @import("uri.zig");
 | 
					const uri_utils = @import("uri.zig");
 | 
				
			||||||
const diff = @import("diff.zig");
 | 
					const diff = @import("diff.zig");
 | 
				
			||||||
const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
 | 
					const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
 | 
				
			||||||
 | 
					const analyser = @import("analyser/analyser.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const data = @import("data/data.zig");
 | 
					const data = @import("data/data.zig");
 | 
				
			||||||
const snipped_data = @import("data/snippets.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();
 | 
					        var err_it = int.errors.iterator();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while (err_it.next()) |err| {
 | 
					        while (err_it.next()) |err| {
 | 
				
			||||||
            try diagnostics.append(allocator, .{
 | 
					            diagnostics.appendAssumeCapacity(.{
 | 
				
			||||||
                .range = offsets.nodeToRange(tree, err.key_ptr.*, server.offset_encoding),
 | 
					                .range = offsets.nodeToRange(tree, err.key_ptr.*, server.offset_encoding),
 | 
				
			||||||
                .severity = .Error,
 | 
					                .severity = .Error,
 | 
				
			||||||
                .code = .{ .string = err.value_ptr.code },
 | 
					                .code = .{ .string = err.value_ptr.code },
 | 
				
			||||||
@ -597,30 +598,10 @@ fn typeToCompletion(
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
        .primitive, .array_index => {},
 | 
					        .primitive, .array_index => {},
 | 
				
			||||||
        .@"comptime" => |co| {
 | 
					        .@"comptime" => |co| {
 | 
				
			||||||
            const ti = co.type.getTypeInfo();
 | 
					            if (type_handle.type.is_type_val) {
 | 
				
			||||||
            switch (ti) {
 | 
					                try analyser.completions.dotCompletions(allocator, list, &co.interpreter.ip, co.value.ty, co.value.val, co.value.node_idx);
 | 
				
			||||||
                .@"struct" => |st| {
 | 
					            } else {
 | 
				
			||||||
                    var fit = st.fields.iterator();
 | 
					                try analyser.completions.dotCompletions(allocator, list, &co.interpreter.ip, co.value.val, .none, co.value.node_idx);
 | 
				
			||||||
                    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 => {},
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -939,7 +920,7 @@ fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle) error{OutO
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const resolved_type_str = if (resolved_type) |rt|
 | 
					    const resolved_type_str = if (resolved_type) |rt|
 | 
				
			||||||
        if (rt.type.is_type_val) switch (rt.type.data) {
 | 
					        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 => "type",
 | 
				
			||||||
        } else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds
 | 
					        } else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds
 | 
				
			||||||
            .pointer,
 | 
					            .pointer,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3601
									
								
								src/analyser/InternPool.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3601
									
								
								src/analyser/InternPool.zig
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										8
									
								
								src/analyser/analyser.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/analyser/analyser.zig
									
									
									
									
									
										Normal file
									
								
							@ -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());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										209
									
								
								src/analyser/completions.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								src/analyser/completions.zig
									
									
									
									
									
										Normal file
									
								
							@ -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,
 | 
				
			||||||
 | 
					    } };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										210
									
								
								src/analyser/encoding.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								src/analyser/encoding.zig
									
									
									
									
									
										Normal file
									
								
							@ -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];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -7,6 +7,7 @@ const URI = @import("uri.zig");
 | 
				
			|||||||
const log = std.log.scoped(.analysis);
 | 
					const log = std.log.scoped(.analysis);
 | 
				
			||||||
const ast = @import("ast.zig");
 | 
					const ast = @import("ast.zig");
 | 
				
			||||||
const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
 | 
					const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
 | 
				
			||||||
 | 
					const InternPool = ComptimeInterpreter.InternPool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var using_trail: std.ArrayList([*]const u8) = undefined;
 | 
					var using_trail: std.ArrayList([*]const u8) = undefined;
 | 
				
			||||||
var resolve_trail: std.ArrayList(NodeWithHandle) = undefined;
 | 
					var resolve_trail: std.ArrayList(NodeWithHandle) = undefined;
 | 
				
			||||||
@ -762,41 +763,39 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, node_handle: NodeWithHan
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    log.info("Invoking interpreter!", .{});
 | 
					                    log.info("Invoking interpreter!", .{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    store.ensureInterpreterExists(handle.uri) catch |err| {
 | 
					                    const interpreter = store.ensureInterpreterExists(handle.uri) catch |err| {
 | 
				
			||||||
                        log.err("Interpreter error: {s}", .{@errorName(err)});
 | 
					                        log.err("Failed to interpret file: {s}", .{@errorName(err)});
 | 
				
			||||||
                        if (@errorReturnTrace()) |trace| {
 | 
					                        if (@errorReturnTrace()) |trace| {
 | 
				
			||||||
                            std.debug.dumpStackTrace(trace.*);
 | 
					                            std.debug.dumpStackTrace(trace.*);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        return null;
 | 
					                        return null;
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
                    var interpreter = handle.interpreter.?;
 | 
					
 | 
				
			||||||
 | 
					                    const root_namespace = @intToEnum(ComptimeInterpreter.Namespace.Index, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    // TODO: Start from current/nearest-current scope
 | 
					                    // TODO: Start from current/nearest-current scope
 | 
				
			||||||
                    const result = interpreter.interpret(node, interpreter.root_type.?.getTypeInfo().getScopeOfType().?, .{}) catch |err| {
 | 
					                    const result = interpreter.interpret(node, root_namespace, .{}) catch |err| {
 | 
				
			||||||
                        log.err("Interpreter error: {s}", .{@errorName(err)});
 | 
					                        log.err("Failed to interpret node: {s}", .{@errorName(err)});
 | 
				
			||||||
                        if (@errorReturnTrace()) |trace| {
 | 
					                        if (@errorReturnTrace()) |trace| {
 | 
				
			||||||
                            std.debug.dumpStackTrace(trace.*);
 | 
					                            std.debug.dumpStackTrace(trace.*);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        return null;
 | 
					                        return null;
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
                    const val = result.getValue() catch |err| {
 | 
					                    const value = result.getValue() catch |err| {
 | 
				
			||||||
                        log.err("Interpreter error: {s}", .{@errorName(err)});
 | 
					                        log.err("interpreter return no result: {s}", .{@errorName(err)});
 | 
				
			||||||
                        if (@errorReturnTrace()) |trace| {
 | 
					                        if (@errorReturnTrace()) |trace| {
 | 
				
			||||||
                            std.debug.dumpStackTrace(trace.*);
 | 
					                            std.debug.dumpStackTrace(trace.*);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        return null;
 | 
					                        return null;
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    const ti = val.type.getTypeInfo();
 | 
					 | 
				
			||||||
                    if (ti != .type) {
 | 
					 | 
				
			||||||
                        log.err("Not a type: { }", .{interpreter.formatTypeInfo(ti)});
 | 
					 | 
				
			||||||
                        return null;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    return TypeWithHandle{
 | 
					                    return TypeWithHandle{
 | 
				
			||||||
                        .type = .{
 | 
					                        .type = .{
 | 
				
			||||||
                            .data = .{ .@"comptime" = .{ .interpreter = interpreter, .type = val.value_data.type } },
 | 
					                            .data = .{ .@"comptime" = .{
 | 
				
			||||||
                            .is_type_val = true,
 | 
					                                .interpreter = interpreter,
 | 
				
			||||||
 | 
					                                .value = value,
 | 
				
			||||||
 | 
					                            } },
 | 
				
			||||||
 | 
					                            .is_type_val = value.ty == InternPool.Index.type_type,
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                        .handle = node_handle.handle,
 | 
					                        .handle = node_handle.handle,
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
@ -1050,7 +1049,7 @@ pub const Type = struct {
 | 
				
			|||||||
        array_index,
 | 
					        array_index,
 | 
				
			||||||
        @"comptime": struct {
 | 
					        @"comptime": struct {
 | 
				
			||||||
            interpreter: *ComptimeInterpreter,
 | 
					            interpreter: *ComptimeInterpreter,
 | 
				
			||||||
            type: ComptimeInterpreter.Type,
 | 
					            value: ComptimeInterpreter.Value,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    /// If true, the type `type`, the attached data is the value of the type value.
 | 
					    /// If true, the type `type`, the attached data is the value of the type value.
 | 
				
			||||||
 | 
				
			|||||||
@ -14,3 +14,9 @@ pub const URI = @import("uri.zig");
 | 
				
			|||||||
pub const DocumentStore = @import("DocumentStore.zig");
 | 
					pub const DocumentStore = @import("DocumentStore.zig");
 | 
				
			||||||
pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
 | 
					pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
 | 
				
			||||||
pub const diff = @import("diff.zig");
 | 
					pub const diff = @import("diff.zig");
 | 
				
			||||||
 | 
					pub const analyser = @import("analyser/analyser.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					comptime {
 | 
				
			||||||
 | 
					    const std = @import("std");
 | 
				
			||||||
 | 
					    std.testing.refAllDecls(@This());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,96 +1,482 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
const zls = @import("zls");
 | 
					const zls = @import("zls");
 | 
				
			||||||
 | 
					const builtin = @import("builtin");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Ast = std.zig.Ast;
 | 
					const Ast = std.zig.Ast;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ComptimeInterpreter = zls.ComptimeInterpreter;
 | 
					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;
 | 
					const allocator: std.mem.Allocator = std.testing.allocator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test "ComptimeInterpreter - basic test" {
 | 
					test "ComptimeInterpreter - primitive types" {
 | 
				
			||||||
    var config = zls.Config{};
 | 
					    try testExpr("true", .{ .simple_type = .bool }, .{ .simple_value = .bool_true });
 | 
				
			||||||
    var doc_store = zls.DocumentStore{
 | 
					    try testExpr("false", .{ .simple_type = .bool }, .{ .simple_value = .bool_false });
 | 
				
			||||||
        .allocator = allocator,
 | 
					    try testExpr("5", .{ .simple_type = .comptime_int }, .{ .int_u64_value = 5 });
 | 
				
			||||||
        .config = &config,
 | 
					    // TODO try testExpr("-2", .{ .simple_type = .comptime_int }, .{ .int_i64_value = -2 });
 | 
				
			||||||
    };
 | 
					    try testExpr("3.0", .{ .simple_type = .comptime_float }, null);
 | 
				
			||||||
    defer doc_store.deinit();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _ = try doc_store.openDocument("file:///file.zig",
 | 
					    try testExpr("null", .{ .simple_type = .null_type }, .{ .simple_value = .null_value });
 | 
				
			||||||
        \\pub fn ReturnMyType(comptime my_arg: bool) type {
 | 
					    try testExpr("void", .{ .simple_type = .type }, .{ .simple_type = .void });
 | 
				
			||||||
        \\    var abc = z: {break :z if (!my_arg) 123 else 0;};
 | 
					    try testExpr("undefined", .{ .simple_type = .undefined_type }, .{ .simple_value = .undefined_value });
 | 
				
			||||||
        \\    if (abc == 123) return u69;
 | 
					    try testExpr("noreturn", .{ .simple_type = .type }, .{ .simple_type = .noreturn });
 | 
				
			||||||
        \\    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())});
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test "ComptimeInterpreter - struct" {
 | 
					test "ComptimeInterpreter - expressions" {
 | 
				
			||||||
    var config = zls.Config{};
 | 
					    if (true) return error.SkipZigTest; // TODO
 | 
				
			||||||
    var doc_store = zls.DocumentStore{
 | 
					    try testExpr("5 + 3", .{ .simple_type = .comptime_int }, .{ .int_u64_value = 8 });
 | 
				
			||||||
        .allocator = allocator,
 | 
					    try testExpr("5.2 + 4.2", .{ .simple_type = .comptime_float }, null);
 | 
				
			||||||
        .config = &config,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    defer doc_store.deinit();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _ = try doc_store.openDocument("file:///file.zig",
 | 
					    try testExpr("3 == 3", .{ .simple_type = .bool }, .{ .simple_valueclear = .bool_true });
 | 
				
			||||||
        \\pub fn ReturnMyType() type {
 | 
					    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 {
 | 
					        \\    return struct {
 | 
				
			||||||
        \\        slay: bool,
 | 
					        \\        slay: bool,
 | 
				
			||||||
        \\        var abc = 123;
 | 
					        \\        var abc = 123;
 | 
				
			||||||
        \\    };
 | 
					        \\    };
 | 
				
			||||||
        \\}
 | 
					        \\}
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					    defer context.deinit();
 | 
				
			||||||
 | 
					    const result = try context.call(context.findFn("Foo"), &.{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var interpreter = ComptimeInterpreter{
 | 
					    try std.testing.expect(result.ty == .simple_type);
 | 
				
			||||||
        .allocator = allocator,
 | 
					    try std.testing.expect(result.ty.simple_type == .type);
 | 
				
			||||||
        .document_store = &doc_store,
 | 
					    const struct_info = context.interpreter.ip.getStruct(result.val.?.struct_type);
 | 
				
			||||||
        .uri = "file:///file.zig",
 | 
					    try std.testing.expectEqual(Index.none, struct_info.backing_int_ty);
 | 
				
			||||||
    };
 | 
					    try std.testing.expectEqual(std.builtin.Type.ContainerLayout.Auto, struct_info.layout);
 | 
				
			||||||
    defer interpreter.deinit();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _ = try interpreter.interpret(0, null, .{});
 | 
					    try std.testing.expectEqual(@as(usize, 1), struct_info.fields.count());
 | 
				
			||||||
 | 
					    try std.testing.expectEqualStrings("slay", struct_info.fields.keys()[0]);
 | 
				
			||||||
    const rmt = interpreter.root_type.?.getTypeInfo().@"struct".scope.declarations.get("ReturnMyType").?;
 | 
					    try std.testing.expect(struct_info.fields.values()[0].ty == Index.bool_type);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
    const z = try interpreter.call(null, rmt.node_idx, &.{}, .{});
 | 
					
 | 
				
			||||||
    defer z.scope.deinit();
 | 
					test "ComptimeInterpreter - call comptime argument" {
 | 
				
			||||||
 | 
					    var context = try Context.init(
 | 
				
			||||||
    try std.testing.expectFmt("struct {slay: bool, var abc: comptime_int = 123, }", "{any}", .{interpreter.formatTypeInfo(z.result.value.value_data.type.getTypeInfo())});
 | 
					        \\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;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user