Merge pull request #1000 from Techatrix/stage2-zir

Embed AstGen into ZLS
This commit is contained in:
Auguste Rame 2023-04-01 23:08:40 -04:00 committed by GitHub
commit 471d971d1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 19132 additions and 294 deletions

View File

@ -79,6 +79,7 @@ The following options are currently available.
| `include_at_in_builtins` | `bool` | `false` | Whether the @ sign should be part of the completion of builtins |
| `skip_std_references` | `bool` | `false` | When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is |
| `max_detail_length` | `usize` | `1048576` | The detail field of completions is truncated to be no longer than this (in bytes) |
| `prefer_ast_check_as_child_process` | `bool` | `true` | Can be used in conjuction with `enable_ast_check_diagnostics` to favor using `zig ast-check` instead of ZLS's fork |
| `record_session` | `bool` | `false` | When true, zls will record all request is receives and write in into `record_session_path`, so that they can replayed with `zls replay` |
| `record_session_path` | `?[]const u8` | `null` | Output file path when `record_session` is set. The recommended file extension *.zlsreplay |
| `replay_session_path` | `?[]const u8` | `null` | Used when calling `zls replay` for specifying the replay file. If no extra argument is given `record_session_path` is used as the default path. |

View File

@ -94,6 +94,11 @@
"type": "integer",
"default": "1048576"
},
"prefer_ast_check_as_child_process": {
"description": "Can be used in conjuction with `enable_ast_check_diagnostics` to favor using `zig ast-check` instead of ZLS's fork",
"type": "boolean",
"default": "true"
},
"record_session": {
"description": "When true, zls will record all request is receives and write in into `record_session_path`, so that they can replayed with `zls replay`",
"type": "boolean",

View File

@ -61,6 +61,9 @@ skip_std_references: bool = false,
/// The detail field of completions is truncated to be no longer than this (in bytes)
max_detail_length: usize = 1048576,
/// Can be used in conjuction with `enable_ast_check_diagnostics` to favor using `zig ast-check` instead of ZLS's fork
prefer_ast_check_as_child_process: bool = true,
/// When true, zls will record all request is receives and write in into `record_session_path`, so that they can replayed with `zls replay`
record_session: bool = false,

View File

@ -13,6 +13,8 @@ const Config = @import("Config.zig");
const ZigVersionWrapper = @import("ZigVersionWrapper.zig");
const translate_c = @import("translate_c.zig");
const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
const AstGen = @import("stage2/AstGen.zig");
const Zir = @import("stage2/Zir.zig");
const DocumentStore = @This();
@ -56,6 +58,13 @@ pub const Handle = struct {
uri: Uri,
text: [:0]const u8,
tree: Ast,
/// do not access unless `zir_status != .none`
zir: Zir = undefined,
zir_status: enum {
none,
outdated,
done,
} = .none,
/// Not null if a ComptimeInterpreter is actually used
interpreter: ?*ComptimeInterpreter = null,
document_scope: analysis.DocumentScope,
@ -74,6 +83,7 @@ pub const Handle = struct {
allocator.destroy(interpreter);
}
self.document_scope.deinit(allocator);
if (self.zir_status != .none) self.zir.deinit(allocator);
self.tree.deinit(allocator);
allocator.free(self.text);
allocator.free(self.uri);
@ -214,6 +224,15 @@ pub fn refreshDocument(self: *DocumentStore, uri: Uri, new_text: [:0]const u8) !
handle.tree.deinit(self.allocator);
handle.tree = new_tree;
if (self.wantZir() and handle.open and new_tree.errors.len == 0) {
const new_zir = try AstGen.generate(self.allocator, new_tree);
if (handle.zir_status != .none) handle.zir.deinit(self.allocator);
handle.zir = new_zir;
handle.zir_status = .done;
} else if (handle.zir_status == .done) {
handle.zir_status = .outdated;
}
var new_document_scope = try analysis.makeDocumentScope(self.allocator, handle.tree);
handle.document_scope.deinit(self.allocator);
handle.document_scope = new_document_scope;
@ -698,17 +717,31 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]const u8, open: bool
var tree = try Ast.parse(self.allocator, text, .zig);
errdefer tree.deinit(self.allocator);
// remove unused capacity
var nodes = tree.nodes.toMultiArrayList();
try nodes.setCapacity(self.allocator, nodes.len);
tree.nodes = nodes.slice();
// remove unused capacity
var tokens = tree.tokens.toMultiArrayList();
try tokens.setCapacity(self.allocator, tokens.len);
tree.tokens = tokens.slice();
const generate_zir = self.wantZir() and open and tree.errors.len == 0;
var zir: ?Zir = if (generate_zir) try AstGen.generate(self.allocator, tree) else null;
errdefer if (zir) |*code| code.deinit(self.allocator);
// remove unused capacity
if (zir) |*code| {
var instructions = code.instructions.toMultiArrayList();
try instructions.setCapacity(self.allocator, instructions.len);
code.instructions = instructions.slice();
}
var document_scope = try analysis.makeDocumentScope(self.allocator, tree);
errdefer document_scope.deinit(self.allocator);
// remove unused capacity
try document_scope.scopes.setCapacity(self.allocator, document_scope.scopes.len);
break :blk Handle{
@ -716,6 +749,8 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]const u8, open: bool
.uri = duped_uri,
.text = text,
.tree = tree,
.zir = if (zir) |code| code else undefined,
.zir_status = if (zir != null) .done else .none,
.document_scope = document_scope,
};
};
@ -1084,6 +1119,12 @@ pub fn enumCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle
return try self.tagStoreCompletionItems(arena, handle, "enum_completions");
}
pub fn wantZir(self: DocumentStore) bool {
if (!self.config.enable_ast_check_diagnostics) return false;
const can_run_ast_check = std.process.can_spawn and self.config.zig_exe_path != null and self.config.prefer_ast_check_as_child_process;
return !can_run_ast_check;
}
pub fn ensureInterpreterExists(self: *DocumentStore, uri: Uri) !*ComptimeInterpreter {
var handle = self.handles.get(uri).?;
if (handle.interpreter != null) return handle.interpreter.?;

View File

@ -29,6 +29,7 @@ const completions = @import("features/completions.zig");
const goto = @import("features/goto.zig");
const hover_handler = @import("features/hover.zig");
const selection_range = @import("features/selection_range.zig");
const diagnostics_gen = @import("features/diagnostics.zig");
const tres = @import("tres");
@ -209,285 +210,6 @@ fn showMessage(
});
}
fn generateDiagnostics(server: *Server, handle: DocumentStore.Handle) error{OutOfMemory}!types.PublishDiagnosticsParams {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
std.debug.assert(server.client_capabilities.supports_publish_diagnostics);
const tree = handle.tree;
var allocator = server.arena.allocator();
var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){};
for (tree.errors) |err| {
var mem_buffer: [256]u8 = undefined;
var fbs = std.io.fixedBufferStream(&mem_buffer);
tree.renderError(err, fbs.writer()) catch if (std.debug.runtime_safety) unreachable else continue; // if an error occurs here increase buffer size
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, err.token, server.offset_encoding),
.severity = .Error,
.code = .{ .string = @tagName(err.tag) },
.source = "zls",
.message = try server.arena.allocator().dupe(u8, fbs.getWritten()),
// .relatedInformation = undefined
});
}
if (server.config.enable_ast_check_diagnostics and tree.errors.len == 0) {
getAstCheckDiagnostics(server, handle, &diagnostics) catch |err| {
log.err("failed to run ast-check: {}", .{err});
};
}
if (server.config.warn_style) {
var node: u32 = 0;
while (node < tree.nodes.len) : (node += 1) {
if (ast.isBuiltinCall(tree, node)) {
const builtin_token = tree.nodes.items(.main_token)[node];
const call_name = tree.tokenSlice(builtin_token);
if (!std.mem.eql(u8, call_name, "@import")) continue;
var buffer: [2]Ast.Node.Index = undefined;
const params = ast.builtinCallParams(tree, node, &buffer).?;
if (params.len != 1) continue;
const import_str_token = tree.nodes.items(.main_token)[params[0]];
const import_str = tree.tokenSlice(import_str_token);
if (std.mem.startsWith(u8, import_str, "\"./")) {
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, import_str_token, server.offset_encoding),
.severity = .Hint,
.code = .{ .string = "dot_slash_import" },
.source = "zls",
.message = "A ./ is not needed in imports",
});
}
}
}
// TODO: style warnings for types, values and declarations below root scope
if (tree.errors.len == 0) {
for (tree.rootDecls()) |decl_idx| {
const decl = tree.nodes.items(.tag)[decl_idx];
switch (decl) {
.fn_proto,
.fn_proto_multi,
.fn_proto_one,
.fn_proto_simple,
.fn_decl,
=> blk: {
var buf: [1]Ast.Node.Index = undefined;
const func = tree.fullFnProto(&buf, decl_idx).?;
if (func.extern_export_inline_token != null) break :blk;
if (func.name_token) |name_token| {
const is_type_function = Analyser.isTypeFunction(tree, func);
const func_name = tree.tokenSlice(name_token);
if (!is_type_function and !Analyser.isCamelCase(func_name)) {
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, name_token, server.offset_encoding),
.severity = .Hint,
.code = .{ .string = "bad_style" },
.source = "zls",
.message = "Functions should be camelCase",
});
} else if (is_type_function and !Analyser.isPascalCase(func_name)) {
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, name_token, server.offset_encoding),
.severity = .Hint,
.code = .{ .string = "bad_style" },
.source = "zls",
.message = "Type functions should be PascalCase",
});
}
}
},
else => {},
}
}
}
}
for (handle.cimports.items(.hash), handle.cimports.items(.node)) |hash, node| {
const result = server.document_store.cimports.get(hash) orelse continue;
if (result != .failure) continue;
const stderr = std.mem.trim(u8, result.failure, " ");
var pos_and_diag_iterator = std.mem.split(u8, stderr, ":");
_ = pos_and_diag_iterator.next(); // skip file path
_ = pos_and_diag_iterator.next(); // skip line
_ = pos_and_diag_iterator.next(); // skip character
try diagnostics.append(allocator, .{
.range = offsets.nodeToRange(handle.tree, node, server.offset_encoding),
.severity = .Error,
.code = .{ .string = "cImport" },
.source = "zls",
.message = try allocator.dupe(u8, pos_and_diag_iterator.rest()),
});
}
if (server.config.highlight_global_var_declarations) {
const main_tokens = tree.nodes.items(.main_token);
const tags = tree.tokens.items(.tag);
for (tree.rootDecls()) |decl| {
const decl_tag = tree.nodes.items(.tag)[decl];
const decl_main_token = tree.nodes.items(.main_token)[decl];
switch (decl_tag) {
.simple_var_decl,
.aligned_var_decl,
.local_var_decl,
.global_var_decl,
=> {
if (tags[main_tokens[decl]] != .keyword_var) continue; // skip anything immutable
// uncomment this to get a list :)
//log.debug("possible global variable \"{s}\"", .{tree.tokenSlice(decl_main_token + 1)});
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, decl_main_token, server.offset_encoding),
.severity = .Hint,
.code = .{ .string = "highlight_global_var_declarations" },
.source = "zls",
.message = "Global var declaration",
});
},
else => {},
}
}
}
if (handle.interpreter) |int| {
try diagnostics.ensureUnusedCapacity(allocator, int.errors.count());
var err_it = int.errors.iterator();
while (err_it.next()) |err| {
diagnostics.appendAssumeCapacity(.{
.range = offsets.nodeToRange(tree, err.key_ptr.*, server.offset_encoding),
.severity = .Error,
.code = .{ .string = err.value_ptr.code },
.source = "zls",
.message = err.value_ptr.message,
});
}
}
// try diagnostics.appendSlice(allocator, handle.interpreter.?.diagnostics.items);
return .{
.uri = handle.uri,
.diagnostics = diagnostics.items,
};
}
fn getAstCheckDiagnostics(
server: *Server,
handle: DocumentStore.Handle,
diagnostics: *std.ArrayListUnmanaged(types.Diagnostic),
) !void {
var allocator = server.arena.allocator();
const zig_exe_path = server.config.zig_exe_path orelse return;
var process = std.ChildProcess.init(&[_][]const u8{ zig_exe_path, "ast-check", "--color", "off" }, server.allocator);
process.stdin_behavior = .Pipe;
process.stderr_behavior = .Pipe;
process.spawn() catch |err| {
log.warn("Failed to spawn zig ast-check process, error: {}", .{err});
return;
};
try process.stdin.?.writeAll(handle.text);
process.stdin.?.close();
process.stdin = null;
const stderr_bytes = try process.stderr.?.reader().readAllAlloc(server.allocator, std.math.maxInt(usize));
defer server.allocator.free(stderr_bytes);
const term = process.wait() catch |err| {
log.warn("Failed to await zig ast-check process, error: {}", .{err});
return;
};
if (term != .Exited) return;
var last_diagnostic: ?types.Diagnostic = null;
// we don't store DiagnosticRelatedInformation in last_diagnostic instead
// its stored in last_related_diagnostics because we need an ArrayList
var last_related_diagnostics: std.ArrayListUnmanaged(types.DiagnosticRelatedInformation) = .{};
// NOTE: I believe that with color off it's one diag per line; is this correct?
var line_iterator = std.mem.split(u8, stderr_bytes, "\n");
while (line_iterator.next()) |line| lin: {
if (!std.mem.startsWith(u8, line, "<stdin>")) continue;
var pos_and_diag_iterator = std.mem.split(u8, line, ":");
const maybe_first = pos_and_diag_iterator.next();
if (maybe_first) |first| {
if (first.len <= 1) break :lin;
} else break;
const utf8_position = types.Position{
.line = (try std.fmt.parseInt(u32, pos_and_diag_iterator.next().?, 10)) - 1,
.character = (try std.fmt.parseInt(u32, pos_and_diag_iterator.next().?, 10)) - 1,
};
// zig uses utf-8 encoding for character offsets
const position = offsets.convertPositionEncoding(handle.text, utf8_position, .@"utf-8", server.offset_encoding);
const range = offsets.tokenPositionToRange(handle.text, position, server.offset_encoding);
const msg = pos_and_diag_iterator.rest()[1..];
if (std.mem.startsWith(u8, msg, "note: ")) {
try last_related_diagnostics.append(allocator, .{
.location = .{
.uri = handle.uri,
.range = range,
},
.message = try server.arena.allocator().dupe(u8, msg["note: ".len..]),
});
continue;
}
if (last_diagnostic) |*diagnostic| {
diagnostic.relatedInformation = try last_related_diagnostics.toOwnedSlice(allocator);
try diagnostics.append(allocator, diagnostic.*);
last_diagnostic = null;
}
if (std.mem.startsWith(u8, msg, "error: ")) {
last_diagnostic = types.Diagnostic{
.range = range,
.severity = .Error,
.code = .{ .string = "ast_check" },
.source = "zls",
.message = try server.arena.allocator().dupe(u8, msg["error: ".len..]),
};
} else {
last_diagnostic = types.Diagnostic{
.range = range,
.severity = .Error,
.code = .{ .string = "ast_check" },
.source = "zls",
.message = try server.arena.allocator().dupe(u8, msg),
};
}
}
if (last_diagnostic) |*diagnostic| {
diagnostic.relatedInformation = try last_related_diagnostics.toOwnedSlice(allocator);
try diagnostics.append(allocator, diagnostic.*);
last_diagnostic = null;
}
}
fn getAutofixMode(server: *Server) enum {
on_save,
will_save_wait_until,
@ -507,12 +229,11 @@ fn getAutofixMode(server: *Server) enum {
/// caller owns returned memory.
pub fn autofix(server: *Server, allocator: std.mem.Allocator, handle: *const DocumentStore.Handle) error{OutOfMemory}!std.ArrayListUnmanaged(types.TextEdit) {
if (!server.config.enable_ast_check_diagnostics) return .{};
if (handle.tree.errors.len != 0) return .{};
var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){};
getAstCheckDiagnostics(server, handle.*, &diagnostics) catch |err| {
log.err("failed to run ast-check: {}", .{err});
};
try diagnostics_gen.getAstCheckDiagnostics(server, handle.*, &diagnostics);
if (diagnostics.items.len == 0) return .{};
var builder = code_actions.Builder{
.arena = server.arena.allocator(),
@ -765,6 +486,16 @@ fn initializeHandler(server: *Server, request: types.InitializeParams) Error!typ
, .{server.config.record_session_path});
}
if (server.config.enable_ast_check_diagnostics and
server.config.prefer_ast_check_as_child_process)
{
if (!std.process.can_spawn) {
log.info("'prefer_ast_check_as_child_process' is ignored because your OS can't spawn a child process", .{});
} else if (server.config.zig_exe_path == null) {
log.info("'prefer_ast_check_as_child_process' is ignored because Zig could not be found", .{});
}
}
return .{
.serverInfo = .{
.name = "zls",
@ -1025,7 +756,7 @@ fn openDocumentHandler(server: *Server, notification: types.DidOpenTextDocumentP
if (server.client_capabilities.supports_publish_diagnostics) blk: {
if (!std.process.can_spawn) break :blk;
const diagnostics = try server.generateDiagnostics(handle);
const diagnostics = try diagnostics_gen.generateDiagnostics(server, handle);
server.sendNotification("textDocument/publishDiagnostics", diagnostics);
}
}
@ -1042,7 +773,7 @@ fn changeDocumentHandler(server: *Server, notification: types.DidChangeTextDocum
if (server.client_capabilities.supports_publish_diagnostics) blk: {
if (!std.process.can_spawn) break :blk;
const diagnostics = try server.generateDiagnostics(handle.*);
const diagnostics = try diagnostics_gen.generateDiagnostics(server, handle.*);
server.sendNotification("textDocument/publishDiagnostics", diagnostics);
}
}
@ -1054,7 +785,7 @@ fn saveDocumentHandler(server: *Server, notification: types.DidSaveTextDocumentP
const handle = server.document_store.getHandle(uri) orelse return;
try server.document_store.applySave(handle);
if (std.process.can_spawn and server.getAutofixMode() == .on_save) {
if (server.getAutofixMode() == .on_save) {
var text_edits = try server.autofix(allocator, handle);
var workspace_edit = types.WorkspaceEdit{ .changes = .{} };
@ -1082,7 +813,6 @@ fn willSaveWaitUntilHandler(server: *Server, request: types.WillSaveTextDocument
const handle = server.document_store.getHandle(request.textDocument.uri) orelse return null;
if (!std.process.can_spawn) return null;
var text_edits = try server.autofix(allocator, handle);
return try text_edits.toOwnedSlice(allocator);
@ -1185,7 +915,7 @@ pub fn hoverHandler(server: *Server, request: types.HoverParams) Error!?types.Ho
// TODO: Figure out a better solution for comptime interpreter diags
if (server.client_capabilities.supports_publish_diagnostics) blk: {
if (!std.process.can_spawn) break :blk;
const diagnostics = try server.generateDiagnostics(handle.*);
const diagnostics = try diagnostics_gen.generateDiagnostics(server, handle.*);
server.sendNotification("textDocument/publishDiagnostics", diagnostics);
}
@ -1459,12 +1189,8 @@ fn codeActionHandler(server: *Server, request: types.CodeActionParams) Error!?[]
// as of right now, only ast-check errors may get a code action
var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){};
if (server.config.enable_ast_check_diagnostics and handle.tree.errors.len == 0) blk: {
if (!std.process.can_spawn) break :blk;
getAstCheckDiagnostics(server, handle.*, &diagnostics) catch |err| {
log.err("failed to run ast-check: {}", .{err});
return error.InternalError;
};
if (server.config.enable_ast_check_diagnostics and handle.tree.errors.len == 0) {
try diagnostics_gen.getAstCheckDiagnostics(server, handle.*, &diagnostics);
}
var actions = std.ArrayListUnmanaged(types.CodeAction){};

View File

@ -107,6 +107,12 @@
"type": "usize",
"default": "1048576"
},
{
"name": "prefer_ast_check_as_child_process",
"description": "Can be used in conjuction with `enable_ast_check_diagnostics` to favor using `zig ast-check` instead of ZLS's fork",
"type": "bool",
"default": "true"
},
{
"name": "record_session",
"description": "When true, zls will record all request is receives and write in into `record_session_path`, so that they can replayed with `zls replay`",

View File

@ -0,0 +1,391 @@
const std = @import("std");
const Ast = std.zig.Ast;
const log = std.log.scoped(.zls_diagnostics);
const Server = @import("../Server.zig");
const DocumentStore = @import("../DocumentStore.zig");
const types = @import("../lsp.zig");
const Analyser = @import("../analysis.zig");
const ast = @import("../ast.zig");
const offsets = @import("../offsets.zig");
const tracy = @import("../tracy.zig");
const Module = @import("../stage2/Module.zig");
const Zir = @import("../stage2/Zir.zig");
pub fn generateDiagnostics(server: *Server, handle: DocumentStore.Handle) error{OutOfMemory}!types.PublishDiagnosticsParams {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
std.debug.assert(server.client_capabilities.supports_publish_diagnostics);
const tree = handle.tree;
var allocator = server.arena.allocator();
var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){};
for (tree.errors) |err| {
var mem_buffer: [256]u8 = undefined;
var fbs = std.io.fixedBufferStream(&mem_buffer);
tree.renderError(err, fbs.writer()) catch if (std.debug.runtime_safety) unreachable else continue; // if an error occurs here increase buffer size
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, err.token, server.offset_encoding),
.severity = .Error,
.code = .{ .string = @tagName(err.tag) },
.source = "zls",
.message = try server.arena.allocator().dupe(u8, fbs.getWritten()),
// .relatedInformation = undefined
});
}
if (server.config.enable_ast_check_diagnostics and tree.errors.len == 0) {
try getAstCheckDiagnostics(server, handle, &diagnostics);
}
if (server.config.warn_style) {
var node: u32 = 0;
while (node < tree.nodes.len) : (node += 1) {
if (ast.isBuiltinCall(tree, node)) {
const builtin_token = tree.nodes.items(.main_token)[node];
const call_name = tree.tokenSlice(builtin_token);
if (!std.mem.eql(u8, call_name, "@import")) continue;
var buffer: [2]Ast.Node.Index = undefined;
const params = ast.builtinCallParams(tree, node, &buffer).?;
if (params.len != 1) continue;
const import_str_token = tree.nodes.items(.main_token)[params[0]];
const import_str = tree.tokenSlice(import_str_token);
if (std.mem.startsWith(u8, import_str, "\"./")) {
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, import_str_token, server.offset_encoding),
.severity = .Hint,
.code = .{ .string = "dot_slash_import" },
.source = "zls",
.message = "A ./ is not needed in imports",
});
}
}
}
// TODO: style warnings for types, values and declarations below root scope
if (tree.errors.len == 0) {
for (tree.rootDecls()) |decl_idx| {
const decl = tree.nodes.items(.tag)[decl_idx];
switch (decl) {
.fn_proto,
.fn_proto_multi,
.fn_proto_one,
.fn_proto_simple,
.fn_decl,
=> blk: {
var buf: [1]Ast.Node.Index = undefined;
const func = tree.fullFnProto(&buf, decl_idx).?;
if (func.extern_export_inline_token != null) break :blk;
if (func.name_token) |name_token| {
const is_type_function = Analyser.isTypeFunction(tree, func);
const func_name = tree.tokenSlice(name_token);
if (!is_type_function and !Analyser.isCamelCase(func_name)) {
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, name_token, server.offset_encoding),
.severity = .Hint,
.code = .{ .string = "bad_style" },
.source = "zls",
.message = "Functions should be camelCase",
});
} else if (is_type_function and !Analyser.isPascalCase(func_name)) {
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, name_token, server.offset_encoding),
.severity = .Hint,
.code = .{ .string = "bad_style" },
.source = "zls",
.message = "Type functions should be PascalCase",
});
}
}
},
else => {},
}
}
}
}
for (handle.cimports.items(.hash), handle.cimports.items(.node)) |hash, node| {
const result = server.document_store.cimports.get(hash) orelse continue;
if (result != .failure) continue;
const stderr = std.mem.trim(u8, result.failure, " ");
var pos_and_diag_iterator = std.mem.split(u8, stderr, ":");
_ = pos_and_diag_iterator.next(); // skip file path
_ = pos_and_diag_iterator.next(); // skip line
_ = pos_and_diag_iterator.next(); // skip character
try diagnostics.append(allocator, .{
.range = offsets.nodeToRange(handle.tree, node, server.offset_encoding),
.severity = .Error,
.code = .{ .string = "cImport" },
.source = "zls",
.message = try allocator.dupe(u8, pos_and_diag_iterator.rest()),
});
}
if (server.config.highlight_global_var_declarations) {
const main_tokens = tree.nodes.items(.main_token);
const tags = tree.tokens.items(.tag);
for (tree.rootDecls()) |decl| {
const decl_tag = tree.nodes.items(.tag)[decl];
const decl_main_token = tree.nodes.items(.main_token)[decl];
switch (decl_tag) {
.simple_var_decl,
.aligned_var_decl,
.local_var_decl,
.global_var_decl,
=> {
if (tags[main_tokens[decl]] != .keyword_var) continue; // skip anything immutable
// uncomment this to get a list :)
//log.debug("possible global variable \"{s}\"", .{tree.tokenSlice(decl_main_token + 1)});
try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, decl_main_token, server.offset_encoding),
.severity = .Hint,
.code = .{ .string = "highlight_global_var_declarations" },
.source = "zls",
.message = "Global var declaration",
});
},
else => {},
}
}
}
if (handle.interpreter) |int| {
try diagnostics.ensureUnusedCapacity(allocator, int.errors.count());
var err_it = int.errors.iterator();
while (err_it.next()) |err| {
diagnostics.appendAssumeCapacity(.{
.range = offsets.nodeToRange(tree, err.key_ptr.*, server.offset_encoding),
.severity = .Error,
.code = .{ .string = err.value_ptr.code },
.source = "zls",
.message = err.value_ptr.message,
});
}
}
// try diagnostics.appendSlice(allocator, handle.interpreter.?.diagnostics.items);
return .{
.uri = handle.uri,
.diagnostics = diagnostics.items,
};
}
pub fn getAstCheckDiagnostics(
server: *Server,
handle: DocumentStore.Handle,
diagnostics: *std.ArrayListUnmanaged(types.Diagnostic),
) error{OutOfMemory}!void {
std.debug.assert(server.config.enable_ast_check_diagnostics);
std.debug.assert(handle.tree.errors.len == 0);
if (server.config.prefer_ast_check_as_child_process and
std.process.can_spawn and
server.config.zig_exe_path != null)
{
getDiagnosticsFromAstCheck(server, handle, diagnostics) catch |err| {
log.err("failed to run ast-check: {}", .{err});
};
} else {
std.debug.assert(server.document_store.wantZir());
switch (handle.zir_status) {
.none, .outdated => {},
.done => try getDiagnosticsFromZir(server, handle, diagnostics),
}
}
}
fn getDiagnosticsFromAstCheck(
server: *Server,
handle: DocumentStore.Handle,
diagnostics: *std.ArrayListUnmanaged(types.Diagnostic),
) !void {
comptime std.debug.assert(std.process.can_spawn);
std.debug.assert(server.config.zig_exe_path != null);
var allocator = server.arena.allocator();
const zig_exe_path = server.config.zig_exe_path.?;
var process = std.ChildProcess.init(&[_][]const u8{ zig_exe_path, "ast-check", "--color", "off" }, server.allocator);
process.stdin_behavior = .Pipe;
process.stderr_behavior = .Pipe;
process.spawn() catch |err| {
log.warn("Failed to spawn zig ast-check process, error: {}", .{err});
return;
};
try process.stdin.?.writeAll(handle.text);
process.stdin.?.close();
process.stdin = null;
const stderr_bytes = try process.stderr.?.reader().readAllAlloc(server.allocator, std.math.maxInt(usize));
defer server.allocator.free(stderr_bytes);
const term = process.wait() catch |err| {
log.warn("Failed to await zig ast-check process, error: {}", .{err});
return;
};
if (term != .Exited) return;
var last_diagnostic: ?types.Diagnostic = null;
// we don't store DiagnosticRelatedInformation in last_diagnostic instead
// its stored in last_related_diagnostics because we need an ArrayList
var last_related_diagnostics: std.ArrayListUnmanaged(types.DiagnosticRelatedInformation) = .{};
// NOTE: I believe that with color off it's one diag per line; is this correct?
var line_iterator = std.mem.split(u8, stderr_bytes, "\n");
while (line_iterator.next()) |line| lin: {
if (!std.mem.startsWith(u8, line, "<stdin>")) continue;
var pos_and_diag_iterator = std.mem.split(u8, line, ":");
const maybe_first = pos_and_diag_iterator.next();
if (maybe_first) |first| {
if (first.len <= 1) break :lin;
} else break;
const utf8_position = types.Position{
.line = (try std.fmt.parseInt(u32, pos_and_diag_iterator.next().?, 10)) - 1,
.character = (try std.fmt.parseInt(u32, pos_and_diag_iterator.next().?, 10)) - 1,
};
// zig uses utf-8 encoding for character offsets
const position = offsets.convertPositionEncoding(handle.text, utf8_position, .@"utf-8", server.offset_encoding);
const range = offsets.tokenPositionToRange(handle.text, position, server.offset_encoding);
const msg = pos_and_diag_iterator.rest()[1..];
if (std.mem.startsWith(u8, msg, "note: ")) {
try last_related_diagnostics.append(allocator, .{
.location = .{
.uri = handle.uri,
.range = range,
},
.message = try server.arena.allocator().dupe(u8, msg["note: ".len..]),
});
continue;
}
if (last_diagnostic) |*diagnostic| {
diagnostic.relatedInformation = try last_related_diagnostics.toOwnedSlice(allocator);
try diagnostics.append(allocator, diagnostic.*);
last_diagnostic = null;
}
if (std.mem.startsWith(u8, msg, "error: ")) {
last_diagnostic = types.Diagnostic{
.range = range,
.severity = .Error,
.code = .{ .string = "ast_check" },
.source = "zls",
.message = try server.arena.allocator().dupe(u8, msg["error: ".len..]),
};
} else {
last_diagnostic = types.Diagnostic{
.range = range,
.severity = .Error,
.code = .{ .string = "ast_check" },
.source = "zls",
.message = try server.arena.allocator().dupe(u8, msg),
};
}
}
if (last_diagnostic) |*diagnostic| {
diagnostic.relatedInformation = try last_related_diagnostics.toOwnedSlice(allocator);
try diagnostics.append(allocator, diagnostic.*);
last_diagnostic = null;
}
}
fn getDiagnosticsFromZir(
server: *Server,
handle: DocumentStore.Handle,
diagnostics: *std.ArrayListUnmanaged(types.Diagnostic),
) error{OutOfMemory}!void {
std.debug.assert(handle.zir_status != .none);
const allocator = server.arena.allocator();
const payload_index = handle.zir.extra[@enumToInt(Zir.ExtraIndex.compile_errors)];
if (payload_index == 0) return;
const header = handle.zir.extraData(Zir.Inst.CompileErrors, payload_index);
const items_len = header.data.items_len;
try diagnostics.ensureUnusedCapacity(allocator, items_len);
var extra_index = header.end;
for (0..items_len) |_| {
const item = handle.zir.extraData(Zir.Inst.CompileErrors.Item, extra_index);
extra_index = item.end;
const err_loc = blk: {
if (item.data.node != 0) {
break :blk offsets.nodeToLoc(handle.tree, item.data.node);
}
const loc = offsets.tokenToLoc(handle.tree, item.data.token);
break :blk offsets.Loc{
.start = loc.start + item.data.byte_offset,
.end = loc.end,
};
};
var notes: []types.DiagnosticRelatedInformation = &.{};
if (item.data.notes != 0) {
const block = handle.zir.extraData(Zir.Inst.Block, item.data.notes);
const body = handle.zir.extra[block.end..][0..block.data.body_len];
notes = try allocator.alloc(types.DiagnosticRelatedInformation, body.len);
for (notes, body) |*note, note_index| {
const note_item = handle.zir.extraData(Zir.Inst.CompileErrors.Item, note_index);
const msg = handle.zir.nullTerminatedString(note_item.data.msg);
const loc = blk: {
if (note_item.data.node != 0) {
break :blk offsets.nodeToLoc(handle.tree, note_item.data.node);
}
const loc = offsets.tokenToLoc(handle.tree, note_item.data.token);
break :blk offsets.Loc{
.start = loc.start + note_item.data.byte_offset,
.end = loc.end,
};
};
note.* = .{
.location = .{
.uri = handle.uri,
.range = offsets.locToRange(handle.text, loc, server.offset_encoding),
},
.message = msg,
};
}
}
const msg = handle.zir.nullTerminatedString(item.data.msg);
diagnostics.appendAssumeCapacity(.{
.range = offsets.locToRange(handle.text, err_loc, server.offset_encoding),
.severity = .Error,
.code = .{ .string = "ast_check" },
.source = "zls",
.message = msg,
.relatedInformation = if (notes.len != 0) notes else null,
});
}
}

12821
src/stage2/AstGen.zig Normal file

File diff suppressed because it is too large Load Diff

1007
src/stage2/BuiltinFn.zig Normal file

File diff suppressed because it is too large Load Diff

21
src/stage2/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (Expat)
Copyright (c) 2015-2023, Zig contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

960
src/stage2/Module.zig Normal file
View File

@ -0,0 +1,960 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const log = std.log.scoped(.module);
const Ast = std.zig.Ast;
const Module = @This();
const DocumentStore = @import("../DocumentStore.zig");
const Handle = DocumentStore.Handle;
/// Canonical reference to a position within a source file.
pub const SrcLoc = struct {
handle: *Handle,
/// Might be 0 depending on tag of `lazy`.
parent_decl_node: Ast.Node.Index,
/// Relative to `parent_decl_node`.
lazy: LazySrcLoc,
pub fn declSrcToken(src_loc: SrcLoc) Ast.TokenIndex {
const tree = src_loc.handle.tree;
return tree.firstToken(src_loc.parent_decl_node);
}
pub fn declRelativeToNodeIndex(src_loc: SrcLoc, offset: i32) Ast.TokenIndex {
return @bitCast(Ast.Node.Index, offset + @bitCast(i32, src_loc.parent_decl_node));
}
pub const Span = struct {
start: u32,
end: u32,
main: u32,
};
pub fn span(src_loc: SrcLoc) Span {
switch (src_loc.lazy) {
.unneeded => unreachable,
.entire_file => return Span{ .start = 0, .end = 1, .main = 0 },
.byte_abs => |byte_index| return Span{ .start = byte_index, .end = byte_index + 1, .main = byte_index },
.token_abs => |tok_index| {
const tree = src_loc.handle.tree;
const start = tree.tokens.items(.start)[tok_index];
const end = start + @intCast(u32, tree.tokenSlice(tok_index).len);
return Span{ .start = start, .end = end, .main = start };
},
.node_abs => |node| {
const tree = src_loc.handle.tree;
return nodeToSpan(tree, node);
},
.byte_offset => |byte_off| {
const tree = src_loc.handle.tree;
const tok_index = src_loc.declSrcToken();
const start = tree.tokens.items(.start)[tok_index] + byte_off;
const end = start + @intCast(u32, tree.tokenSlice(tok_index).len);
return Span{ .start = start, .end = end, .main = start };
},
.token_offset => |tok_off| {
const tree = src_loc.handle.tree;
const tok_index = src_loc.declSrcToken() + tok_off;
const start = tree.tokens.items(.start)[tok_index];
const end = start + @intCast(u32, tree.tokenSlice(tok_index).len);
return Span{ .start = start, .end = end, .main = start };
},
.node_offset => |traced_off| {
const node_off = traced_off.x;
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
assert(src_loc.handle.tree_loaded);
return nodeToSpan(tree, node);
},
.node_offset_main_token => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const main_token = tree.nodes.items(.main_token)[node];
return tokensToSpan(tree, main_token, main_token, main_token);
},
.node_offset_bin_op => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
assert(src_loc.handle.tree_loaded);
return nodeToSpan(tree, node);
},
.node_offset_initializer => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
return tokensToSpan(
tree,
tree.firstToken(node) - 3,
tree.lastToken(node),
tree.nodes.items(.main_token)[node] - 2,
);
},
.node_offset_var_decl_ty => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const node_tags = tree.nodes.items(.tag);
const full = switch (node_tags[node]) {
.global_var_decl,
.local_var_decl,
.simple_var_decl,
.aligned_var_decl,
=> tree.fullVarDecl(node).?,
.@"usingnamespace" => {
const node_data = tree.nodes.items(.data);
return nodeToSpan(tree, node_data[node].lhs);
},
else => unreachable,
};
if (full.ast.type_node != 0) {
return nodeToSpan(tree, full.ast.type_node);
}
const tok_index = full.ast.mut_token + 1; // the name token
const start = tree.tokens.items(.start)[tok_index];
const end = start + @intCast(u32, tree.tokenSlice(tok_index).len);
return Span{ .start = start, .end = end, .main = start };
},
.node_offset_var_decl_align => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullVarDecl(node).?;
return nodeToSpan(tree, full.ast.align_node);
},
.node_offset_var_decl_section => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullVarDecl(node).?;
return nodeToSpan(tree, full.ast.section_node);
},
.node_offset_var_decl_addrspace => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullVarDecl(node).?;
return nodeToSpan(tree, full.ast.addrspace_node);
},
.node_offset_var_decl_init => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullVarDecl(node).?;
return nodeToSpan(tree, full.ast.init_node);
},
.node_offset_builtin_call_arg0 => |n| return src_loc.byteOffsetBuiltinCallArg(n, 0),
.node_offset_builtin_call_arg1 => |n| return src_loc.byteOffsetBuiltinCallArg(n, 1),
.node_offset_builtin_call_arg2 => |n| return src_loc.byteOffsetBuiltinCallArg(n, 2),
.node_offset_builtin_call_arg3 => |n| return src_loc.byteOffsetBuiltinCallArg(n, 3),
.node_offset_builtin_call_arg4 => |n| return src_loc.byteOffsetBuiltinCallArg(n, 4),
.node_offset_builtin_call_arg5 => |n| return src_loc.byteOffsetBuiltinCallArg(n, 5),
.node_offset_array_access_index => |node_off| {
const tree = src_loc.handle.tree;
const node_datas = tree.nodes.items(.data);
const node = src_loc.declRelativeToNodeIndex(node_off);
return nodeToSpan(tree, node_datas[node].rhs);
},
.node_offset_slice_ptr,
.node_offset_slice_start,
.node_offset_slice_end,
.node_offset_slice_sentinel,
=> |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullSlice(node).?;
const part_node = switch (src_loc.lazy) {
.node_offset_slice_ptr => full.ast.sliced,
.node_offset_slice_start => full.ast.start,
.node_offset_slice_end => full.ast.end,
.node_offset_slice_sentinel => full.ast.sentinel,
else => unreachable,
};
return nodeToSpan(tree, part_node);
},
.node_offset_call_func => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullCall(&buf, node).?;
return nodeToSpan(tree, full.ast.fn_expr);
},
.node_offset_field_name => |node_off| {
const tree = src_loc.handle.tree;
const node_datas = tree.nodes.items(.data);
const node_tags = tree.nodes.items(.tag);
const node = src_loc.declRelativeToNodeIndex(node_off);
const tok_index = switch (node_tags[node]) {
.field_access => node_datas[node].rhs,
else => tree.firstToken(node) - 2,
};
const start = tree.tokens.items(.start)[tok_index];
const end = start + @intCast(u32, tree.tokenSlice(tok_index).len);
return Span{ .start = start, .end = end, .main = start };
},
.node_offset_deref_ptr => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
return nodeToSpan(tree, node);
},
.node_offset_asm_source => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullAsm(node).?;
return nodeToSpan(tree, full.ast.template);
},
.node_offset_asm_ret_ty => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullAsm(node).?;
const asm_output = full.outputs[0];
const node_datas = tree.nodes.items(.data);
return nodeToSpan(tree, node_datas[asm_output].lhs);
},
.node_offset_for_cond, .node_offset_if_cond => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const node_tags = tree.nodes.items(.tag);
const src_node = switch (node_tags[node]) {
.if_simple,
.@"if",
=> tree.fullIf(node).?.ast.cond_expr,
.while_simple,
.while_cont,
.@"while",
.for_simple,
.@"for",
=> tree.fullWhile(node).?.ast.cond_expr,
.@"orelse" => node,
.@"catch" => node,
else => unreachable,
};
return nodeToSpan(tree, src_node);
},
.node_offset_bin_lhs => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const node_datas = tree.nodes.items(.data);
return nodeToSpan(tree, node_datas[node].lhs);
},
.node_offset_bin_rhs => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const node_datas = tree.nodes.items(.data);
return nodeToSpan(tree, node_datas[node].rhs);
},
.node_offset_switch_operand => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
const node_datas = tree.nodes.items(.data);
return nodeToSpan(tree, node_datas[node].lhs);
},
.node_offset_switch_special_prong => |node_off| {
const tree = src_loc.handle.tree;
const switch_node = src_loc.declRelativeToNodeIndex(node_off);
const node_datas = tree.nodes.items(.data);
const node_tags = tree.nodes.items(.tag);
const main_tokens = tree.nodes.items(.main_token);
const extra = tree.extraData(node_datas[switch_node].rhs, Ast.Node.SubRange);
const case_nodes = tree.extra_data[extra.start..extra.end];
for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?;
const is_special = (case.ast.values.len == 0) or
(case.ast.values.len == 1 and
node_tags[case.ast.values[0]] == .identifier and
std.mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"));
if (!is_special) continue;
return nodeToSpan(tree, case_node);
} else unreachable;
},
.node_offset_switch_range => |node_off| {
const tree = src_loc.handle.tree;
const switch_node = src_loc.declRelativeToNodeIndex(node_off);
const node_datas = tree.nodes.items(.data);
const node_tags = tree.nodes.items(.tag);
const main_tokens = tree.nodes.items(.main_token);
const extra = tree.extraData(node_datas[switch_node].rhs, Ast.Node.SubRange);
const case_nodes = tree.extra_data[extra.start..extra.end];
for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?;
const is_special = (case.ast.values.len == 0) or
(case.ast.values.len == 1 and
node_tags[case.ast.values[0]] == .identifier and
std.mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"));
if (is_special) continue;
for (case.ast.values) |item_node| {
if (node_tags[item_node] == .switch_range) {
return nodeToSpan(tree, item_node);
}
}
} else unreachable;
},
.node_offset_switch_prong_capture => |node_off| {
const tree = src_loc.handle.tree;
const case_node = src_loc.declRelativeToNodeIndex(node_off);
const case = tree.fullSwitchCase(case_node).?;
const start_tok = case.payload_token.?;
const token_tags = tree.tokens.items(.tag);
const end_tok = switch (token_tags[start_tok]) {
.asterisk => start_tok + 1,
else => start_tok,
};
const start = tree.tokens.items(.start)[start_tok];
const end_start = tree.tokens.items(.start)[end_tok];
const end = end_start + @intCast(u32, tree.tokenSlice(end_tok).len);
return Span{ .start = start, .end = end, .main = start };
},
.node_offset_fn_type_align => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
return nodeToSpan(tree, full.ast.align_expr);
},
.node_offset_fn_type_addrspace => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
return nodeToSpan(tree, full.ast.addrspace_expr);
},
.node_offset_fn_type_section => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
return nodeToSpan(tree, full.ast.section_expr);
},
.node_offset_fn_type_cc => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
return nodeToSpan(tree, full.ast.callconv_expr);
},
.node_offset_fn_type_ret_ty => |node_off| {
const tree = src_loc.handle.tree;
const node = src_loc.declRelativeToNodeIndex(node_off);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
return nodeToSpan(tree, full.ast.return_type);
},
.node_offset_param => |node_off| {
const tree = src_loc.handle.tree;
const token_tags = tree.tokens.items(.tag);
const node = src_loc.declRelativeToNodeIndex(node_off);
var first_tok = tree.firstToken(node);
while (true) switch (token_tags[first_tok - 1]) {
.colon, .identifier, .keyword_comptime, .keyword_noalias => first_tok -= 1,
else => break,
};
return tokensToSpan(
tree,
first_tok,
tree.lastToken(node),
first_tok,
);
},
.token_offset_param => |token_off| {
const tree = src_loc.handle.tree;
const token_tags = tree.tokens.items(.tag);
const main_token = tree.nodes.items(.main_token)[src_loc.parent_decl_node];
const tok_index = @bitCast(Ast.TokenIndex, token_off + @bitCast(i32, main_token));
var first_tok = tok_index;
while (true) switch (token_tags[first_tok - 1]) {
.colon, .identifier, .keyword_comptime, .keyword_noalias => first_tok -= 1,
else => break,
};
return tokensToSpan(
tree,
first_tok,
tok_index,
first_tok,
);
},
.node_offset_anyframe_type => |node_off| {
const tree = src_loc.handle.tree;
const node_datas = tree.nodes.items(.data);
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
return nodeToSpan(tree, node_datas[parent_node].rhs);
},
.node_offset_lib_name => |node_off| {
const tree = src_loc.handle.tree;
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, parent_node).?;
const tok_index = full.lib_name.?;
const start = tree.tokens.items(.start)[tok_index];
const end = start + @intCast(u32, tree.tokenSlice(tok_index).len);
return Span{ .start = start, .end = end, .main = start };
},
.node_offset_array_type_len => |node_off| {
const tree = src_loc.handle.tree;
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullArrayType(parent_node).?;
return nodeToSpan(tree, full.ast.elem_count);
},
.node_offset_array_type_sentinel => |node_off| {
const tree = src_loc.handle.tree;
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullArrayType(parent_node).?;
return nodeToSpan(tree, full.ast.sentinel);
},
.node_offset_array_type_elem => |node_off| {
const tree = src_loc.handle.tree;
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullArrayType(parent_node).?;
return nodeToSpan(tree, full.ast.elem_type);
},
.node_offset_un_op => |node_off| {
const tree = src_loc.handle.tree;
const node_datas = tree.nodes.items(.data);
const node = src_loc.declRelativeToNodeIndex(node_off);
return nodeToSpan(tree, node_datas[node].lhs);
},
.node_offset_ptr_elem => |node_off| {
const tree = src_loc.handle.tree;
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullPtrType(parent_node).?;
return nodeToSpan(tree, full.ast.child_type);
},
.node_offset_ptr_sentinel => |node_off| {
const tree = src_loc.handle.tree;
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullPtrType(parent_node).?;
return nodeToSpan(tree, full.ast.sentinel);
},
.node_offset_ptr_align => |node_off| {
const tree = src_loc.handle.tree;
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullPtrType(parent_node).?;
return nodeToSpan(tree, full.ast.align_node);
},
.node_offset_ptr_addrspace => |node_off| {
const tree = src_loc.handle.tree;
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullPtrType(parent_node).?;
return nodeToSpan(tree, full.ast.addrspace_node);
},
.node_offset_ptr_bitoffset => |node_off| {
const tree = src_loc.handle.tree;
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullPtrType(parent_node).?;
return nodeToSpan(tree, full.ast.bit_range_start);
},
.node_offset_ptr_hostsize => |node_off| {
const tree = src_loc.handle.tree;
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
const full = tree.fullPtrType(parent_node).?;
return nodeToSpan(tree, full.ast.bit_range_end);
},
.node_offset_container_tag => |node_off| {
const tree = src_loc.handle.tree;
const node_tags = tree.nodes.items(.tag);
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
switch (node_tags[parent_node]) {
.container_decl_arg, .container_decl_arg_trailing => {
const full = tree.containerDeclArg(parent_node);
return nodeToSpan(tree, full.ast.arg);
},
.tagged_union_enum_tag, .tagged_union_enum_tag_trailing => {
const full = tree.taggedUnionEnumTag(parent_node);
return tokensToSpan(
tree,
tree.firstToken(full.ast.arg) - 2,
tree.lastToken(full.ast.arg) + 1,
tree.nodes.items(.main_token)[full.ast.arg],
);
},
else => unreachable,
}
},
.node_offset_field_default => |node_off| {
const tree = src_loc.handle.tree;
const node_tags = tree.nodes.items(.tag);
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
const full: Ast.full.ContainerField = switch (node_tags[parent_node]) {
.container_field => tree.containerField(parent_node),
.container_field_init => tree.containerFieldInit(parent_node),
else => unreachable,
};
return nodeToSpan(tree, full.ast.value_expr);
},
.node_offset_init_ty => |node_off| {
const tree = src_loc.handle.tree;
const parent_node = src_loc.declRelativeToNodeIndex(node_off);
var buf: [2]Ast.Node.Index = undefined;
const full = tree.fullArrayInit(&buf, parent_node).?;
return nodeToSpan(tree, full.ast.type_expr);
},
.node_offset_store_ptr => |node_off| {
const tree = src_loc.handle.tree;
const node_tags = tree.nodes.items(.tag);
const node_datas = tree.nodes.items(.data);
const node = src_loc.declRelativeToNodeIndex(node_off);
switch (node_tags[node]) {
.assign => {
return nodeToSpan(tree, node_datas[node].lhs);
},
else => return nodeToSpan(tree, node),
}
},
.node_offset_store_operand => |node_off| {
const tree = src_loc.handle.tree;
const node_tags = tree.nodes.items(.tag);
const node_datas = tree.nodes.items(.data);
const node = src_loc.declRelativeToNodeIndex(node_off);
switch (node_tags[node]) {
.assign => {
return nodeToSpan(tree, node_datas[node].rhs);
},
else => return nodeToSpan(tree, node),
}
},
}
}
pub fn byteOffsetBuiltinCallArg(
src_loc: SrcLoc,
node_off: i32,
arg_index: u32,
) !Span {
const tree = src_loc.handle.tree;
const node_datas = tree.nodes.items(.data);
const node_tags = tree.nodes.items(.tag);
const node = src_loc.declRelativeToNodeIndex(node_off);
const param = switch (node_tags[node]) {
.builtin_call_two, .builtin_call_two_comma => switch (arg_index) {
0 => node_datas[node].lhs,
1 => node_datas[node].rhs,
else => unreachable,
},
.builtin_call, .builtin_call_comma => tree.extra_data[node_datas[node].lhs + arg_index],
else => unreachable,
};
return nodeToSpan(tree, param);
}
pub fn nodeToSpan(tree: *const Ast, node: u32) Span {
return tokensToSpan(
tree,
tree.firstToken(node),
tree.lastToken(node),
tree.nodes.items(.main_token)[node],
);
}
fn tokensToSpan(tree: *const Ast, start: Ast.TokenIndex, end: Ast.TokenIndex, main: Ast.TokenIndex) Span {
const token_starts = tree.tokens.items(.start);
var start_tok = start;
var end_tok = end;
if (tree.tokensOnSameLine(start, end)) {
// do nothing
} else if (tree.tokensOnSameLine(start, main)) {
end_tok = main;
} else if (tree.tokensOnSameLine(main, end)) {
start_tok = main;
} else {
start_tok = main;
end_tok = main;
}
const start_off = token_starts[start_tok];
const end_off = token_starts[end_tok] + @intCast(u32, tree.tokenSlice(end_tok).len);
return Span{ .start = start_off, .end = end_off, .main = token_starts[main] };
}
};
/// Resolving a source location into a byte offset may require doing work
/// that we would rather not do unless the error actually occurs.
/// Therefore we need a data structure that contains the information necessary
/// to lazily produce a `SrcLoc` as required.
/// Most of the offsets in this data structure are relative to the containing Decl.
/// This makes the source location resolve properly even when a Decl gets
/// shifted up or down in the file, as long as the Decl's contents itself
/// do not change.
pub const LazySrcLoc = union(enum) {
/// When this tag is set, the code that constructed this `LazySrcLoc` is asserting
/// that all code paths which would need to resolve the source location are
/// unreachable. If you are debugging this tag incorrectly being this value,
/// look into using reverse-continue with a memory watchpoint to see where the
/// value is being set to this tag.
unneeded,
/// Means the source location points to an entire file; not any particular
/// location within the file. `handle` union field will be active.
entire_file,
/// The source location points to a byte offset within a source file,
/// offset from 0. The source file is determined contextually.
/// Inside a `SrcLoc`, the `handle` union field will be active.
byte_abs: u32,
/// The source location points to a token within a source file,
/// offset from 0. The source file is determined contextually.
/// Inside a `SrcLoc`, the `handle` union field will be active.
token_abs: u32,
/// The source location points to an AST node within a source file,
/// offset from 0. The source file is determined contextually.
/// Inside a `SrcLoc`, the `handle` union field will be active.
node_abs: u32,
/// The source location points to a byte offset within a source file,
/// offset from the byte offset of the Decl within the file.
/// The Decl is determined contextually.
byte_offset: u32,
/// This data is the offset into the token list from the Decl token.
/// The Decl is determined contextually.
token_offset: u32,
/// The source location points to an AST node, which is this value offset
/// from its containing Decl node AST index.
/// The Decl is determined contextually.
node_offset: i32,
/// The source location points to the main token of an AST node, found
/// by taking this AST node index offset from the containing Decl AST node.
/// The Decl is determined contextually.
node_offset_main_token: i32,
/// The source location points to the beginning of a struct initializer.
/// The Decl is determined contextually.
node_offset_initializer: i32,
/// The source location points to a variable declaration type expression,
/// found by taking this AST node index offset from the containing
/// Decl AST node, which points to a variable declaration AST node. Next, navigate
/// to the type expression.
/// The Decl is determined contextually.
node_offset_var_decl_ty: i32,
/// The source location points to the alignment expression of a var decl.
/// The Decl is determined contextually.
node_offset_var_decl_align: i32,
/// The source location points to the linksection expression of a var decl.
/// The Decl is determined contextually.
node_offset_var_decl_section: i32,
/// The source location points to the addrspace expression of a var decl.
/// The Decl is determined contextually.
node_offset_var_decl_addrspace: i32,
/// The source location points to the initializer of a var decl.
/// The Decl is determined contextually.
node_offset_var_decl_init: i32,
/// The source location points to a for loop condition expression,
/// found by taking this AST node index offset from the containing
/// Decl AST node, which points to a for loop AST node. Next, navigate
/// to the condition expression.
/// The Decl is determined contextually.
node_offset_for_cond: i32,
/// The source location points to the first parameter of a builtin
/// function call, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a builtin call AST node. Next, navigate
/// to the first parameter.
/// The Decl is determined contextually.
node_offset_builtin_call_arg0: i32,
/// Same as `node_offset_builtin_call_arg0` except arg index 1.
node_offset_builtin_call_arg1: i32,
node_offset_builtin_call_arg2: i32,
node_offset_builtin_call_arg3: i32,
node_offset_builtin_call_arg4: i32,
node_offset_builtin_call_arg5: i32,
/// The source location points to the index expression of an array access
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to an array access AST node. Next, navigate
/// to the index expression.
/// The Decl is determined contextually.
node_offset_array_access_index: i32,
/// The source location points to the LHS of a slice expression
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a slice AST node. Next, navigate
/// to the sentinel expression.
/// The Decl is determined contextually.
node_offset_slice_ptr: i32,
/// The source location points to start expression of a slice expression
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a slice AST node. Next, navigate
/// to the sentinel expression.
/// The Decl is determined contextually.
node_offset_slice_start: i32,
/// The source location points to the end expression of a slice
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a slice AST node. Next, navigate
/// to the sentinel expression.
/// The Decl is determined contextually.
node_offset_slice_end: i32,
/// The source location points to the sentinel expression of a slice
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a slice AST node. Next, navigate
/// to the sentinel expression.
/// The Decl is determined contextually.
node_offset_slice_sentinel: i32,
/// The source location points to the callee expression of a function
/// call expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a function call AST node. Next, navigate
/// to the callee expression.
/// The Decl is determined contextually.
node_offset_call_func: i32,
/// The payload is offset from the containing Decl AST node.
/// The source location points to the field name of:
/// * a field access expression (`a.b`), or
/// * the operand ("b" node) of a field initialization expression (`.a = b`)
/// The Decl is determined contextually.
node_offset_field_name: i32,
/// The source location points to the pointer of a pointer deref expression,
/// found by taking this AST node index offset from the containing
/// Decl AST node, which points to a pointer deref AST node. Next, navigate
/// to the pointer expression.
/// The Decl is determined contextually.
node_offset_deref_ptr: i32,
/// The source location points to the assembly source code of an inline assembly
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to inline assembly AST node. Next, navigate
/// to the asm template source code.
/// The Decl is determined contextually.
node_offset_asm_source: i32,
/// The source location points to the return type of an inline assembly
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to inline assembly AST node. Next, navigate
/// to the return type expression.
/// The Decl is determined contextually.
node_offset_asm_ret_ty: i32,
/// The source location points to the condition expression of an if
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to an if expression AST node. Next, navigate
/// to the condition expression.
/// The Decl is determined contextually.
node_offset_if_cond: i32,
/// The source location points to a binary expression, such as `a + b`, found
/// by taking this AST node index offset from the containing Decl AST node.
/// The Decl is determined contextually.
node_offset_bin_op: i32,
/// The source location points to the LHS of a binary expression, found
/// by taking this AST node index offset from the containing Decl AST node,
/// which points to a binary expression AST node. Next, navigate to the LHS.
/// The Decl is determined contextually.
node_offset_bin_lhs: i32,
/// The source location points to the RHS of a binary expression, found
/// by taking this AST node index offset from the containing Decl AST node,
/// which points to a binary expression AST node. Next, navigate to the RHS.
/// The Decl is determined contextually.
node_offset_bin_rhs: i32,
/// The source location points to the operand of a switch expression, found
/// by taking this AST node index offset from the containing Decl AST node,
/// which points to a switch expression AST node. Next, navigate to the operand.
/// The Decl is determined contextually.
node_offset_switch_operand: i32,
/// The source location points to the else/`_` prong of a switch expression, found
/// by taking this AST node index offset from the containing Decl AST node,
/// which points to a switch expression AST node. Next, navigate to the else/`_` prong.
/// The Decl is determined contextually.
node_offset_switch_special_prong: i32,
/// The source location points to all the ranges of a switch expression, found
/// by taking this AST node index offset from the containing Decl AST node,
/// which points to a switch expression AST node. Next, navigate to any of the
/// range nodes. The error applies to all of them.
/// The Decl is determined contextually.
node_offset_switch_range: i32,
/// The source location points to the capture of a switch_prong.
/// The Decl is determined contextually.
node_offset_switch_prong_capture: i32,
/// The source location points to the align expr of a function type
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a function type AST node. Next, navigate to
/// the calling convention node.
/// The Decl is determined contextually.
node_offset_fn_type_align: i32,
/// The source location points to the addrspace expr of a function type
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a function type AST node. Next, navigate to
/// the calling convention node.
/// The Decl is determined contextually.
node_offset_fn_type_addrspace: i32,
/// The source location points to the linksection expr of a function type
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a function type AST node. Next, navigate to
/// the calling convention node.
/// The Decl is determined contextually.
node_offset_fn_type_section: i32,
/// The source location points to the calling convention of a function type
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a function type AST node. Next, navigate to
/// the calling convention node.
/// The Decl is determined contextually.
node_offset_fn_type_cc: i32,
/// The source location points to the return type of a function type
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a function type AST node. Next, navigate to
/// the return type node.
/// The Decl is determined contextually.
node_offset_fn_type_ret_ty: i32,
node_offset_param: i32,
token_offset_param: i32,
/// The source location points to the type expression of an `anyframe->T`
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a `anyframe->T` expression AST node. Next, navigate
/// to the type expression.
/// The Decl is determined contextually.
node_offset_anyframe_type: i32,
/// The source location points to the string literal of `extern "foo"`, found
/// by taking this AST node index offset from the containing
/// Decl AST node, which points to a function prototype or variable declaration
/// expression AST node. Next, navigate to the string literal of the `extern "foo"`.
/// The Decl is determined contextually.
node_offset_lib_name: i32,
/// The source location points to the len expression of an `[N:S]T`
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to an `[N:S]T` expression AST node. Next, navigate
/// to the len expression.
/// The Decl is determined contextually.
node_offset_array_type_len: i32,
/// The source location points to the sentinel expression of an `[N:S]T`
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to an `[N:S]T` expression AST node. Next, navigate
/// to the sentinel expression.
/// The Decl is determined contextually.
node_offset_array_type_sentinel: i32,
/// The source location points to the elem expression of an `[N:S]T`
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to an `[N:S]T` expression AST node. Next, navigate
/// to the elem expression.
/// The Decl is determined contextually.
node_offset_array_type_elem: i32,
/// The source location points to the operand of an unary expression.
/// The Decl is determined contextually.
node_offset_un_op: i32,
/// The source location points to the elem type of a pointer.
/// The Decl is determined contextually.
node_offset_ptr_elem: i32,
/// The source location points to the sentinel of a pointer.
/// The Decl is determined contextually.
node_offset_ptr_sentinel: i32,
/// The source location points to the align expr of a pointer.
/// The Decl is determined contextually.
node_offset_ptr_align: i32,
/// The source location points to the addrspace expr of a pointer.
/// The Decl is determined contextually.
node_offset_ptr_addrspace: i32,
/// The source location points to the bit-offset of a pointer.
/// The Decl is determined contextually.
node_offset_ptr_bitoffset: i32,
/// The source location points to the host size of a pointer.
/// The Decl is determined contextually.
node_offset_ptr_hostsize: i32,
/// The source location points to the tag type of an union or an enum.
/// The Decl is determined contextually.
node_offset_container_tag: i32,
/// The source location points to the default value of a field.
/// The Decl is determined contextually.
node_offset_field_default: i32,
/// The source location points to the type of an array or struct initializer.
/// The Decl is determined contextually.
node_offset_init_ty: i32,
/// The source location points to the LHS of an assignment.
/// The Decl is determined contextually.
node_offset_store_ptr: i32,
/// The source location points to the RHS of an assignment.
/// The Decl is determined contextually.
node_offset_store_operand: i32,
pub fn nodeOffset(node_offset: i32) LazySrcLoc {
return .{ .node_offset = node_offset };
}
pub fn toSrcLoc(lazy: LazySrcLoc, handle: *Handle, src_node: Ast.Node.Index) SrcLoc {
return switch (lazy) {
.unneeded,
.entire_file,
.byte_abs,
.token_abs,
.node_abs,
=> .{
.handle = handle,
.parent_decl_node = 0,
.lazy = lazy,
},
.byte_offset,
.token_offset,
.node_offset,
.node_offset_main_token,
.node_offset_initializer,
.node_offset_var_decl_ty,
.node_offset_var_decl_align,
.node_offset_var_decl_section,
.node_offset_var_decl_addrspace,
.node_offset_var_decl_init,
.node_offset_for_cond,
.node_offset_builtin_call_arg0,
.node_offset_builtin_call_arg1,
.node_offset_builtin_call_arg2,
.node_offset_builtin_call_arg3,
.node_offset_builtin_call_arg4,
.node_offset_builtin_call_arg5,
.node_offset_array_access_index,
.node_offset_slice_ptr,
.node_offset_slice_start,
.node_offset_slice_end,
.node_offset_slice_sentinel,
.node_offset_call_func,
.node_offset_field_name,
.node_offset_deref_ptr,
.node_offset_asm_source,
.node_offset_asm_ret_ty,
.node_offset_if_cond,
.node_offset_bin_op,
.node_offset_bin_lhs,
.node_offset_bin_rhs,
.node_offset_switch_operand,
.node_offset_switch_special_prong,
.node_offset_switch_range,
.node_offset_switch_prong_capture,
.node_offset_fn_type_align,
.node_offset_fn_type_addrspace,
.node_offset_fn_type_section,
.node_offset_fn_type_cc,
.node_offset_fn_type_ret_ty,
.node_offset_param,
.token_offset_param,
.node_offset_anyframe_type,
.node_offset_lib_name,
.node_offset_array_type_len,
.node_offset_array_type_sentinel,
.node_offset_array_type_elem,
.node_offset_un_op,
.node_offset_ptr_elem,
.node_offset_ptr_sentinel,
.node_offset_ptr_align,
.node_offset_ptr_addrspace,
.node_offset_ptr_bitoffset,
.node_offset_ptr_hostsize,
.node_offset_container_tag,
.node_offset_field_default,
.node_offset_init_ty,
.node_offset_store_ptr,
.node_offset_store_operand,
=> .{
.handle = handle,
.parent_decl_node = src_node,
.lazy = lazy,
},
};
}
};

3856
src/stage2/Zir.zig Normal file

File diff suppressed because it is too large Load Diff