Merge pull request #724 from zigtools/comptime-bebe-steps
Implement comptime interpreter
This commit is contained in:
commit
355d56376f
1469
src/ComptimeInterpreter.zig
Normal file
1469
src/ComptimeInterpreter.zig
Normal file
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@ const BuildConfig = @import("special/build_runner.zig").BuildConfig;
|
|||||||
const tracy = @import("tracy.zig");
|
const tracy = @import("tracy.zig");
|
||||||
const Config = @import("Config.zig");
|
const Config = @import("Config.zig");
|
||||||
const translate_c = @import("translate_c.zig");
|
const translate_c = @import("translate_c.zig");
|
||||||
|
const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
|
||||||
|
|
||||||
const DocumentStore = @This();
|
const DocumentStore = @This();
|
||||||
|
|
||||||
@ -55,6 +56,8 @@ pub const Handle = struct {
|
|||||||
uri: Uri,
|
uri: Uri,
|
||||||
text: [:0]const u8,
|
text: [:0]const u8,
|
||||||
tree: Ast,
|
tree: Ast,
|
||||||
|
/// Not null if a ComptimeInterpreter is actually used
|
||||||
|
interpreter: ?*ComptimeInterpreter = null,
|
||||||
document_scope: analysis.DocumentScope,
|
document_scope: analysis.DocumentScope,
|
||||||
/// Contains one entry for every import in the document
|
/// Contains one entry for every import in the document
|
||||||
import_uris: std.ArrayListUnmanaged(Uri) = .{},
|
import_uris: std.ArrayListUnmanaged(Uri) = .{},
|
||||||
@ -189,6 +192,12 @@ pub fn refreshDocument(self: *DocumentStore, uri: Uri, new_text: [:0]const u8) !
|
|||||||
|
|
||||||
const handle = self.handles.get(uri) orelse unreachable;
|
const handle = self.handles.get(uri) orelse unreachable;
|
||||||
|
|
||||||
|
// TODO: Handle interpreter cross reference
|
||||||
|
if (handle.interpreter) |int| {
|
||||||
|
int.deinit();
|
||||||
|
handle.interpreter = null;
|
||||||
|
}
|
||||||
|
|
||||||
self.allocator.free(handle.text);
|
self.allocator.free(handle.text);
|
||||||
handle.text = new_text;
|
handle.text = new_text;
|
||||||
|
|
||||||
@ -927,3 +936,17 @@ pub fn errorCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handl
|
|||||||
pub fn enumCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle: Handle) ![]types.CompletionItem {
|
pub fn enumCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle: Handle) ![]types.CompletionItem {
|
||||||
return try self.tagStoreCompletionItems(arena, handle, "enum_completions");
|
return try self.tagStoreCompletionItems(arena, handle, "enum_completions");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ensureInterpreterExists(self: *DocumentStore, uri: Uri) !void {
|
||||||
|
var handle = self.handles.get(uri).?;
|
||||||
|
if (handle.interpreter == null) {
|
||||||
|
var int = try self.allocator.create(ComptimeInterpreter);
|
||||||
|
int.* = ComptimeInterpreter{
|
||||||
|
.allocator = self.allocator,
|
||||||
|
.document_store = self,
|
||||||
|
.uri = uri,
|
||||||
|
};
|
||||||
|
handle.interpreter = int;
|
||||||
|
_ = try int.interpret(0, null, .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ const Ast = std.zig.Ast;
|
|||||||
const tracy = @import("tracy.zig");
|
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 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");
|
||||||
@ -322,6 +323,23 @@ fn publishDiagnostics(server: *Server, writer: anytype, handle: DocumentStore.Ha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (handle.interpreter) |int| {
|
||||||
|
try diagnostics.ensureUnusedCapacity(allocator, int.errors.count());
|
||||||
|
|
||||||
|
var err_it = int.errors.iterator();
|
||||||
|
|
||||||
|
while (err_it.next()) |err| {
|
||||||
|
try diagnostics.append(allocator, .{
|
||||||
|
.range = offsets.nodeToRange(tree, err.key_ptr.*, server.offset_encoding),
|
||||||
|
.severity = .Error,
|
||||||
|
.code = err.value_ptr.code,
|
||||||
|
.source = "zls",
|
||||||
|
.message = err.value_ptr.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// try diagnostics.appendSlice(allocator, handle.interpreter.?.diagnostics.items);
|
||||||
|
|
||||||
try send(writer, server.arena.allocator(), types.Notification{
|
try send(writer, server.arena.allocator(), types.Notification{
|
||||||
.method = "textDocument/publishDiagnostics",
|
.method = "textDocument/publishDiagnostics",
|
||||||
.params = .{
|
.params = .{
|
||||||
@ -485,6 +503,33 @@ fn typeToCompletion(
|
|||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
.primitive, .array_index => {},
|
.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 => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -803,7 +848,10 @@ fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle) error{OutO
|
|||||||
const resolved_type = try decl_handle.resolveType(&server.document_store, &server.arena, &bound_type_params);
|
const resolved_type = try decl_handle.resolveType(&server.document_store, &server.arena, &bound_type_params);
|
||||||
|
|
||||||
const resolved_type_str = if (resolved_type) |rt|
|
const resolved_type_str = if (resolved_type) |rt|
|
||||||
if (rt.type.is_type_val) "type" else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds
|
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())}),
|
||||||
|
else => "type",
|
||||||
|
} else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds
|
||||||
.pointer,
|
.pointer,
|
||||||
.slice,
|
.slice,
|
||||||
.error_union,
|
.error_union,
|
||||||
@ -2023,6 +2071,8 @@ fn hoverHandler(server: *Server, writer: anytype, id: types.RequestId, req: requ
|
|||||||
};
|
};
|
||||||
|
|
||||||
const hover = maybe_hover orelse return try respondGeneric(writer, id, null_result_response);
|
const hover = maybe_hover orelse return try respondGeneric(writer, id, null_result_response);
|
||||||
|
// TODO: Figure out a better solution for comptime interpreter diags
|
||||||
|
try server.publishDiagnostics(writer, handle.*);
|
||||||
|
|
||||||
try send(writer, server.arena.allocator(), types.Response{
|
try send(writer, server.arena.allocator(), types.Response{
|
||||||
.id = id,
|
.id = id,
|
||||||
|
@ -5,6 +5,7 @@ const types = @import("types.zig");
|
|||||||
const offsets = @import("offsets.zig");
|
const offsets = @import("offsets.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");
|
||||||
|
|
||||||
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;
|
||||||
@ -306,7 +307,7 @@ pub fn getDeclNameToken(tree: Ast, node: Ast.Node.Index) ?Ast.TokenIndex {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getDeclName(tree: Ast, node: Ast.Node.Index) ?[]const u8 {
|
pub fn getDeclName(tree: Ast, node: Ast.Node.Index) ?[]const u8 {
|
||||||
const name = tree.tokenSlice(getDeclNameToken(tree, node) orelse return null);
|
const name = tree.tokenSlice(getDeclNameToken(tree, node) orelse return null);
|
||||||
return switch (tree.nodes.items(.tag)[node]) {
|
return switch (tree.nodes.items(.tag)[node]) {
|
||||||
.test_decl => name[1 .. name.len - 1],
|
.test_decl => name[1 .. name.len - 1],
|
||||||
@ -491,7 +492,7 @@ fn resolveUnwrapErrorType(store: *DocumentStore, arena: *std.heap.ArenaAllocator
|
|||||||
.type = .{ .data = .{ .other = n }, .is_type_val = rhs.type.is_type_val },
|
.type = .{ .data = .{ .other = n }, .is_type_val = rhs.type.is_type_val },
|
||||||
.handle = rhs.handle,
|
.handle = rhs.handle,
|
||||||
},
|
},
|
||||||
.primitive, .slice, .pointer, .array_index => return null,
|
.primitive, .slice, .pointer, .array_index, .@"comptime" => return null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (rhs.handle.tree.nodes.items(.tag)[rhs_node] == .error_union) {
|
if (rhs.handle.tree.nodes.items(.tag)[rhs_node] == .error_union) {
|
||||||
@ -742,7 +743,60 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl
|
|||||||
|
|
||||||
const has_body = decl.handle.tree.nodes.items(.tag)[decl_node] == .fn_decl;
|
const has_body = decl.handle.tree.nodes.items(.tag)[decl_node] == .fn_decl;
|
||||||
const body = decl.handle.tree.nodes.items(.data)[decl_node].rhs;
|
const body = decl.handle.tree.nodes.items(.data)[decl_node].rhs;
|
||||||
return try resolveReturnType(store, arena, fn_decl, decl.handle, bound_type_params, if (has_body) body else null);
|
if (try resolveReturnType(store, arena, fn_decl, decl.handle, bound_type_params, if (has_body) body else null)) |ret| {
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
// TODO: Better case-by-case; we just use the ComptimeInterpreter when all else fails,
|
||||||
|
// probably better to use it more liberally
|
||||||
|
// TODO: Handle non-isolate args; e.g. `const T = u8; TypeFunc(T);`
|
||||||
|
// var interpreter = ComptimeInterpreter{ .tree = tree, .allocator = arena.allocator() };
|
||||||
|
|
||||||
|
// var top_decl = try (try interpreter.interpret(0, null, .{})).getValue();
|
||||||
|
// var top_scope = interpreter.typeToTypeInfo(top_decl.@"type".info_idx).@"struct".scope;
|
||||||
|
|
||||||
|
// var fn_decl_scope = top_scope.getParentScopeFromNode(node);
|
||||||
|
|
||||||
|
log.info("Invoking interpreter!", .{});
|
||||||
|
|
||||||
|
store.ensureInterpreterExists(handle.uri) catch |err| {
|
||||||
|
log.err("Interpreter error: {s}", .{@errorName(err)});
|
||||||
|
if (@errorReturnTrace()) |trace| {
|
||||||
|
std.debug.dumpStackTrace(trace.*);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
var interpreter = handle.interpreter.?;
|
||||||
|
|
||||||
|
// 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)});
|
||||||
|
if (@errorReturnTrace()) |trace| {
|
||||||
|
std.debug.dumpStackTrace(trace.*);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const val = result.getValue() catch |err| {
|
||||||
|
log.err("Interpreter error: {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,
|
||||||
|
},
|
||||||
|
.handle = node_handle.handle,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@ -965,6 +1019,10 @@ pub const Type = struct {
|
|||||||
other: Ast.Node.Index,
|
other: Ast.Node.Index,
|
||||||
primitive: Ast.Node.Index,
|
primitive: Ast.Node.Index,
|
||||||
array_index,
|
array_index,
|
||||||
|
@"comptime": struct {
|
||||||
|
interpreter: *ComptimeInterpreter,
|
||||||
|
type: ComptimeInterpreter.Type,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
/// 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.
|
||||||
is_type_val: bool,
|
is_type_val: bool,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
// used by tests as a package
|
// Used by tests as a package, can be used by tools such as
|
||||||
|
// zigbot9001 to take advantage of zls' tools
|
||||||
|
|
||||||
pub const analysis = @import("analysis.zig");
|
pub const analysis = @import("analysis.zig");
|
||||||
pub const header = @import("header.zig");
|
pub const header = @import("header.zig");
|
||||||
pub const offsets = @import("offsets.zig");
|
pub const offsets = @import("offsets.zig");
|
||||||
@ -8,3 +10,5 @@ pub const Server = @import("Server.zig");
|
|||||||
pub const translate_c = @import("translate_c.zig");
|
pub const translate_c = @import("translate_c.zig");
|
||||||
pub const types = @import("types.zig");
|
pub const types = @import("types.zig");
|
||||||
pub const URI = @import("uri.zig");
|
pub const URI = @import("uri.zig");
|
||||||
|
pub const DocumentStore = @import("DocumentStore.zig");
|
||||||
|
pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
|
||||||
|
96
tests/language_features/comptime_interpreter.zig
Normal file
96
tests/language_features/comptime_interpreter.zig
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const zls = @import("zls");
|
||||||
|
|
||||||
|
const Ast = std.zig.Ast;
|
||||||
|
|
||||||
|
const ComptimeInterpreter = zls.ComptimeInterpreter;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
_ = 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())});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - struct" {
|
||||||
|
var config = zls.Config{};
|
||||||
|
var doc_store = zls.DocumentStore{
|
||||||
|
.allocator = allocator,
|
||||||
|
.config = &config,
|
||||||
|
};
|
||||||
|
defer doc_store.deinit();
|
||||||
|
|
||||||
|
_ = try doc_store.openDocument("file:///file.zig",
|
||||||
|
\\pub fn ReturnMyType() type {
|
||||||
|
\\ return struct {
|
||||||
|
\\ slay: bool,
|
||||||
|
\\ var abc = 123;
|
||||||
|
\\ };
|
||||||
|
\\}
|
||||||
|
);
|
||||||
|
|
||||||
|
var interpreter = ComptimeInterpreter{
|
||||||
|
.allocator = allocator,
|
||||||
|
.document_store = &doc_store,
|
||||||
|
.uri = "file:///file.zig",
|
||||||
|
};
|
||||||
|
defer interpreter.deinit();
|
||||||
|
|
||||||
|
_ = 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())});
|
||||||
|
}
|
@ -17,4 +17,5 @@ comptime {
|
|||||||
|
|
||||||
// Language features
|
// Language features
|
||||||
_ = @import("language_features/cimport.zig");
|
_ = @import("language_features/cimport.zig");
|
||||||
|
_ = @import("language_features/comptime_interpreter.zig");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user