From 66092239c22e9f5f3d53dfbdadbc50bc0ddeaca6 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 4 Dec 2022 12:17:19 +0000 Subject: [PATCH 01/21] update flake Current one is too old: [matklad@Ishmael:~/p/tb/zls]$ zig build thread 5181 panic: Your Zig version v0.11.0-dev.38+b40fc7018 does not meet the minimum build requirement of v0.11.0-dev.323+30eb2a175 --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index f081163..99cfc96 100644 --- a/flake.lock +++ b/flake.lock @@ -68,11 +68,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1667292599, - "narHash": "sha256-7ISOUI1aj6UKMPIL+wwthENL22L3+A9V+jS8Is3QsRo=", + "lastModified": 1670086663, + "narHash": "sha256-hT8C8AQB74tdoCPwz4nlJypLMD7GI2F5q+vn+VE/qQk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ef2f213d9659a274985778bff4ca322f3ef3ac68", + "rev": "813836d64fa57285d108f0dbf2356457ccd304e3", "type": "github" }, "original": { @@ -99,11 +99,11 @@ ] }, "locked": { - "lastModified": 1667477360, - "narHash": "sha256-EE1UMXQr4FkQFGoOHDslqi3Q7V2BUkLMrOBeV/UJYoE=", + "lastModified": 1670113356, + "narHash": "sha256-43aMRMU0OuBin6M2LM+nxVG+whazyHuHnUvu92xoth0=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "c8900204fcd5c09ab0c9b7bb7f4d04dacab3c462", + "rev": "17352071583eda4be43fa2a312f6e061326374f7", "type": "github" }, "original": { From 3cac23f8a123e75c4ece91d5dc20984bfda022fe Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Sat, 10 Dec 2022 23:21:55 +0200 Subject: [PATCH 02/21] Fix references to now removed, previously deprecated std.ascii declarations --- build.zig | 2 +- src/Server.zig | 2 +- src/analysis.zig | 2 +- src/code_actions.zig | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.zig b/build.zig index 1007ad0..097bd7b 100644 --- a/build.zig +++ b/build.zig @@ -6,7 +6,7 @@ const zls_version = std.builtin.Version{ .major = 0, .minor = 11, .patch = 0 }; pub fn build(b: *std.build.Builder) !void { const current_zig = builtin.zig_version; - const min_zig = std.SemanticVersion.parse("0.11.0-dev.399+44ee1c885") catch return; // whereabouts allocgate 2.0 + const min_zig = std.SemanticVersion.parse("0.11.0-dev.715+cffbb32d3") catch return; // whereabouts allocgate 2.0 if (current_zig.order(min_zig).compare(.lt)) @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); const target = b.standardTargetOptions(.{}); diff --git a/src/Server.zig b/src/Server.zig index 7aabb99..e040a4e 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -795,7 +795,7 @@ pub fn identifierFromPosition(pos_index: usize, handle: DocumentStore.Handle) [] } fn isSymbolChar(char: u8) bool { - return std.ascii.isAlNum(char) or char == '_'; + return std.ascii.isAlphanumeric(char) or char == '_'; } fn gotoDefinitionSymbol( diff --git a/src/analysis.zig b/src/analysis.zig index 757d602..1e32ae4 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -77,7 +77,7 @@ pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments: while (true) : (curr_line_tok += 1) { const comm = tokens[curr_line_tok]; if ((container_doc and comm == .container_doc_comment) or (!container_doc and comm == .doc_comment)) { - try lines.append(std.mem.trim(u8, tree.tokenSlice(curr_line_tok)[3..], &std.ascii.spaces)); + try lines.append(std.mem.trim(u8, tree.tokenSlice(curr_line_tok)[3..], &std.ascii.whitespace)); } else break; } diff --git a/src/code_actions.zig b/src/code_actions.zig index 74ead7c..bb7160a 100644 --- a/src/code_actions.zig +++ b/src/code_actions.zig @@ -299,7 +299,7 @@ fn createDiscardText(builder: *Builder, identifier_name: []const u8, declaration const indent = find_indent: { const line = offsets.lineSliceUntilIndex(builder.handle.text, declaration_start); for(line) |char, i| { - if(!std.ascii.isSpace(char)) { + if(!std.ascii.isWhitespace(char)) { break :find_indent line[0..i]; } } @@ -600,5 +600,5 @@ fn getCaptureLoc(text: []const u8, loc: offsets.Loc, is_index_payload: bool) ?Ca } fn isSymbolChar(char: u8) bool { - return std.ascii.isAlNum(char) or char == '_'; + return std.ascii.isAlphanumeric(char) or char == '_'; } From e322ca4fb521353bc62ca1756a5a8ac513b0cb0f Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Sat, 10 Dec 2022 23:23:39 +0200 Subject: [PATCH 03/21] Fix build.zig comment --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 097bd7b..a56203a 100644 --- a/build.zig +++ b/build.zig @@ -6,7 +6,7 @@ const zls_version = std.builtin.Version{ .major = 0, .minor = 11, .patch = 0 }; pub fn build(b: *std.build.Builder) !void { const current_zig = builtin.zig_version; - const min_zig = std.SemanticVersion.parse("0.11.0-dev.715+cffbb32d3") catch return; // whereabouts allocgate 2.0 + const min_zig = std.SemanticVersion.parse("0.11.0-dev.715+cffbb32d3") catch return; // Deprecated ascii symbols removed if (current_zig.order(min_zig).compare(.lt)) @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); const target = b.standardTargetOptions(.{}); From 1ae341850ec0b48dddd5c0d25316ace95c92a67a Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sun, 11 Dec 2022 12:10:48 +0100 Subject: [PATCH 04/21] automatically generate config associated files (#813) --- README.md | 41 ++--- build.zig | 12 ++ schema.json | 77 +++++----- src/Config.zig | 279 +++++----------------------------- src/Server.zig | 37 +++-- src/config_gen/config.json | 158 +++++++++++++++++++ src/config_gen/config_gen.zig | 239 +++++++++++++++++++++++++++++ src/configuration.zig | 210 +++++++++++++++++++++++++ src/main.zig | 7 +- src/requests.zig | 6 + src/setup.zig | 178 +++------------------- 11 files changed, 772 insertions(+), 472 deletions(-) create mode 100644 src/config_gen/config.json create mode 100644 src/config_gen/config_gen.zig create mode 100644 src/configuration.zig diff --git a/README.md b/README.md index a98fdf5..806e60c 100644 --- a/README.md +++ b/README.md @@ -59,29 +59,32 @@ zls will look for a zls.json configuration file in multiple locations with the f The following options are currently available. + | Option | Type | Default value | What it Does | | --- | --- | --- | --- | -| `enable_snippets` | `bool` | `false` | Enables snippet completions when the client also supports them. | -| `enable_ast_check_diagnostics` | `bool` | `true`| Whether to enable ast-check diagnostics | -| `enable_autofix` | `bool` | `false`| Whether to automatically fix errors on save. Currently supports adding and removing discards. | +| `enable_snippets` | `bool` | `false` | Enables snippet completions when the client also supports them | +| `enable_ast_check_diagnostics` | `bool` | `true` | Whether to enable ast-check diagnostics | +| `enable_autofix` | `bool` | `false` | Whether to automatically fix errors on save. Currently supports adding and removing discards. | | `enable_import_embedfile_argument_completions` | `bool` | `false` | Whether to enable import/embedFile argument completions | -| `zig_lib_path` | `?[]const u8` | `null` | zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports. | -| `zig_exe_path` | `?[]const u8` | `null` | zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided. | -| `warn_style` | `bool` | `false` | Enables warnings for style *guideline* mismatches | -| `build_runner_path` | `?[]const u8` | `null` | Path to the build_runner.zig file provided by zls. `null` is equivalent to `${executable_directory}/build_runner.zig` | -| `global_cache_path` | `?[]const u8` | `null` | Path to a directroy that will be used as zig's cache. `null` is equivalent to `${KnownFloders.Cache}/zls` | -| `enable_semantic_tokens` | `bool` | `true` | Enables semantic token support when the client also supports it. | -| `enable_inlay_hints` | `bool` | `false` | Enables inlay hint support when the client also supports it. | +| `enable_semantic_tokens` | `bool` | `true` | Enables semantic token support when the client also supports it | +| `enable_inlay_hints` | `bool` | `false` | Enables inlay hint support when the client also supports it | | `inlay_hints_show_builtin` | `bool` | `true` | Enable inlay hints for builtin functions | -| `inlay_hints_exclude_single_argument` | `bool` | `true`| Don't show inlay hints for single argument calls | -| `inlay_hints_hide_redundant_param_names` | `bool` | `false`| Hides inlay hints when parameter name matches the identifier (e.g. foo: foo) | -| `inlay_hints_hide_redundant_param_names_last_token` | `bool` | `false`| Hides inlay hints when parameter name matches the last token of a parameter node (e.g. foo: bar.foo, foo: &foo) | -| `operator_completions` | `bool` | `true` | Enables `*` and `?` operators in completion lists. | -|`include_at_in_builtins`|`bool`|`false`| Whether the @ sign should be part of the completion of builtins. -|`max_detail_length`|`usize`|`1024 * 1024`| The detail field of completions is truncated to be no longer than this (in bytes). -| `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. -| `highlight_global_var_declarations` | `bool` | `false` | Whether to highlight global var declarations. -| `use_comptime_interpreter` | `bool` | `false` | Whether to use the comptime interpreter. +| `inlay_hints_exclude_single_argument` | `bool` | `true` | Don't show inlay hints for single argument calls | +| `inlay_hints_hide_redundant_param_names` | `bool` | `false` | Hides inlay hints when parameter name matches the identifier (e.g. foo: foo) | +| `inlay_hints_hide_redundant_param_names_last_token` | `bool` | `false` | Hides inlay hints when parameter name matches the last token of a parameter node (e.g. foo: bar.foo, foo: &foo) | +| `operator_completions` | `bool` | `true` | Enables `*` and `?` operators in completion lists | +| `warn_style` | `bool` | `false` | Enables warnings for style guideline mismatches | +| `highlight_global_var_declarations` | `bool` | `false` | Whether to highlight global var declarations | +| `use_comptime_interpreter` | `bool` | `false` | Whether to use the comptime interpreter | +| `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) | +| `builtin_path` | `?[]const u8` | `null` | Path to 'builtin;' useful for debugging, automatically set if let null | +| `zig_lib_path` | `?[]const u8` | `null` | Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports | +| `zig_exe_path` | `?[]const u8` | `null` | Zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided | +| `build_runner_path` | `?[]const u8` | `null` | Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig` | +| `global_cache_path` | `?[]const u8` | `null` | Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls` | + ### Per-build Configuration Options diff --git a/build.zig b/build.zig index 1007ad0..baf89bb 100644 --- a/build.zig +++ b/build.zig @@ -116,6 +116,18 @@ pub fn build(b: *std.build.Builder) !void { exe.setBuildMode(mode); exe.install(); + const gen_exe = b.addExecutable("zls_gen", "src/config_gen/config_gen.zig"); + + const gen_cmd = gen_exe.run(); + gen_cmd.addArgs(&.{ + b.fmt("{s}/src/Config.zig", .{b.build_root}), + b.fmt("{s}/schema.json", .{b.build_root}), + b.fmt("{s}/README.md", .{b.build_root}), + }); + + const gen_step = b.step("gen", "Regenerate config files"); + gen_step.dependOn(&gen_cmd.step); + const test_step = b.step("test", "Run all the tests"); test_step.dependOn(b.getInstallStep()); diff --git a/schema.json b/schema.json index f400f26..ce0c552 100644 --- a/schema.json +++ b/schema.json @@ -24,27 +24,6 @@ "type": "boolean", "default": "false" }, - "zig_lib_path": { - "description": "Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports", - "type": "string" - }, - "zig_exe_path": { - "description": "Zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided", - "type": "string" - }, - "warn_style": { - "description": "Enables warnings for style guideline mismatches", - "type": "boolean", - "default": "false" - }, - "build_runner_path": { - "description": "Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig`", - "type": "string" - }, - "global_cache_path": { - "description": "Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls`", - "type": "string" - }, "enable_semantic_tokens": { "description": "Enables semantic token support when the client also supports it", "type": "boolean", @@ -80,18 +59,8 @@ "type": "boolean", "default": "true" }, - "include_at_in_builtins": { - "description": "Whether the @ sign should be part of the completion of builtins", - "type": "boolean", - "default": "false" - }, - "max_detail_length": { - "description": "The detail field of completions is truncated to be no longer than this (in bytes)", - "type": "integer", - "default": "1048576" - }, - "skip_std_references": { - "description": "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", + "warn_style": { + "description": "Enables warnings for style guideline mismatches", "type": "boolean", "default": "false" }, @@ -104,6 +73,46 @@ "description": "Whether to use the comptime interpreter", "type": "boolean", "default": "false" + }, + "include_at_in_builtins": { + "description": "Whether the @ sign should be part of the completion of builtins", + "type": "boolean", + "default": "false" + }, + "skip_std_references": { + "description": "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", + "type": "boolean", + "default": "false" + }, + "max_detail_length": { + "description": "The detail field of completions is truncated to be no longer than this (in bytes)", + "type": "integer", + "default": "1048576" + }, + "builtin_path": { + "description": "Path to 'builtin;' useful for debugging, automatically set if let null", + "type": "string", + "default": "null" + }, + "zig_lib_path": { + "description": "Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports", + "type": "string", + "default": "null" + }, + "zig_exe_path": { + "description": "Zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided", + "type": "string", + "default": "null" + }, + "build_runner_path": { + "description": "Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig`", + "type": "string", + "default": "null" + }, + "global_cache_path": { + "description": "Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls`", + "type": "string", + "default": "null" } } -} +} diff --git a/src/Config.zig b/src/Config.zig index aa56127..55af114 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -1,284 +1,73 @@ +//! DO NOT EDIT //! Configuration options for zls. -//! Keep in sync with schema.json and zls-vscode's package.json! +//! If you want to add a config option edit +//! src/config_gen/config.zig and run `zig build gen` +//! GENERATED BY src/config_gen/config_gen.zig -const Config = @This(); - -const std = @import("std"); -const setup = @import("setup.zig"); -const tracy = @import("tracy.zig"); -const known_folders = @import("known-folders"); - -const logger = std.log.scoped(.config); - -/// Whether to enable snippet completions +/// Enables snippet completions when the client also supports them enable_snippets: bool = false, /// Whether to enable ast-check diagnostics enable_ast_check_diagnostics: bool = true, -/// Whether to automatically fix errors on save. -/// Currently supports adding and removing discards. +/// Whether to automatically fix errors on save. Currently supports adding and removing discards. enable_autofix: bool = false, -/// Whether to enable import/embedFile argument completions (NOTE: these are triggered manually as updating the autotrigger characters may cause issues) +/// Whether to enable import/embedFile argument completions enable_import_embedfile_argument_completions: bool = false, -/// Zig library path -zig_lib_path: ?[]const u8 = null, - -/// Zig executable path used to run the custom build runner. -/// May be used to find a lib path if none is provided. -zig_exe_path: ?[]const u8 = null, - -/// Whether to pay attention to style issues. This is opt-in since the style -/// guide explicitly states that the style info provided is a guideline only. -warn_style: bool = false, - -/// Path to the build_runner.zig file. -build_runner_path: ?[]const u8 = null, - -/// Path to the global cache directory -global_cache_path: ?[]const u8 = null, - -/// Semantic token support +/// Enables semantic token support when the client also supports it enable_semantic_tokens: bool = true, -/// Inlay hint support +/// Enables inlay hint support when the client also supports it enable_inlay_hints: bool = false, -/// enable inlay hints for builtin functions +/// Enable inlay hints for builtin functions inlay_hints_show_builtin: bool = true, -/// don't show inlay hints for single argument calls +/// Don't show inlay hints for single argument calls inlay_hints_exclude_single_argument: bool = true, -/// don't show inlay hints when parameter name matches the identifier -/// for example: `foo: foo` +/// Hides inlay hints when parameter name matches the identifier (e.g. foo: foo) inlay_hints_hide_redundant_param_names: bool = false, -/// don't show inlay hints when parameter names matches the last -/// for example: `foo: bar.foo`, `foo: &foo` +/// Hides inlay hints when parameter name matches the last token of a parameter node (e.g. foo: bar.foo, foo: &foo) inlay_hints_hide_redundant_param_names_last_token: bool = false, -/// Whether to enable `*` and `?` operators in completion lists +/// Enables `*` and `?` operators in completion lists operator_completions: bool = true, -/// Whether the @ sign should be part of the completion of builtins -include_at_in_builtins: bool = false, +/// Enables warnings for style guideline mismatches +warn_style: bool = false, -/// The detail field of completions is truncated to be no longer than this (in bytes). -max_detail_length: usize = 1048576, - -/// Skips references to std. This will improve lookup speeds. -/// Going to definition however will continue to work -skip_std_references: bool = false, - -/// Path to "builtin;" useful for debugging, automatically set if let null -builtin_path: ?[]const u8 = null, - -/// Whether to highlight global var declarations. +/// Whether to highlight global var declarations highlight_global_var_declarations: bool = false, /// Whether to use the comptime interpreter use_comptime_interpreter: bool = false, -pub fn loadFromFile(allocator: std.mem.Allocator, file_path: []const u8) ?Config { - const tracy_zone = tracy.trace(@src()); - defer tracy_zone.end(); +/// Whether the @ sign should be part of the completion of builtins +include_at_in_builtins: bool = false, - var file = std.fs.cwd().openFile(file_path, .{}) catch |err| { - if (err != error.FileNotFound) - logger.warn("Error while reading configuration file: {}", .{err}); - return null; - }; +/// 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 +skip_std_references: bool = false, - defer file.close(); +/// The detail field of completions is truncated to be no longer than this (in bytes) +max_detail_length: usize = 1048576, - const file_buf = file.readToEndAlloc(allocator, 0x1000000) catch return null; - defer allocator.free(file_buf); - @setEvalBranchQuota(10000); +/// Path to 'builtin;' useful for debugging, automatically set if let null +builtin_path: ?[]const u8 = null, - var token_stream = std.json.TokenStream.init(file_buf); - const parse_options = std.json.ParseOptions{ .allocator = allocator, .ignore_unknown_fields = true }; +/// Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports +zig_lib_path: ?[]const u8 = null, - // TODO: Better errors? Doesn't seem like std.json can provide us positions or context. - var config = std.json.parse(Config, &token_stream, parse_options) catch |err| { - logger.warn("Error while parsing configuration file: {}", .{err}); - return null; - }; +/// Zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided +zig_exe_path: ?[]const u8 = null, - if (config.zig_lib_path) |zig_lib_path| { - if (!std.fs.path.isAbsolute(zig_lib_path)) { - logger.warn("zig library path is not absolute, defaulting to null.", .{}); - allocator.free(zig_lib_path); - config.zig_lib_path = null; - } - } +/// Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig` +build_runner_path: ?[]const u8 = null, - return config; -} +/// Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls` +global_cache_path: ?[]const u8 = null, -pub fn loadFromFolder(allocator: std.mem.Allocator, folder_path: []const u8) ?Config { - const tracy_zone = tracy.trace(@src()); - defer tracy_zone.end(); - - const full_path = std.fs.path.resolve(allocator, &.{ folder_path, "zls.json" }) catch return null; - defer allocator.free(full_path); - return loadFromFile(allocator, full_path); -} - -/// Invoke this once all config values have been changed. -pub fn configChanged(config: *Config, allocator: std.mem.Allocator, builtin_creation_dir: ?[]const u8) !void { - // Find the zig executable in PATH - find_zig: { - if (config.zig_exe_path) |exe_path| { - if (std.fs.path.isAbsolute(exe_path)) not_valid: { - std.fs.cwd().access(exe_path, .{}) catch break :not_valid; - break :find_zig; - } - logger.debug("zig path `{s}` is not absolute, will look in path", .{exe_path}); - allocator.free(exe_path); - } - config.zig_exe_path = try setup.findZig(allocator); - } - - if (config.zig_exe_path) |exe_path| blk: { - logger.info("Using zig executable {s}", .{exe_path}); - - if (config.zig_lib_path != null) break :blk; - - var env = getZigEnv(allocator, exe_path) orelse break :blk; - defer std.json.parseFree(Env, env, .{ .allocator = allocator }); - - // Make sure the path is absolute - config.zig_lib_path = try std.fs.realpathAlloc(allocator, env.lib_dir.?); - logger.info("Using zig lib path '{s}'", .{config.zig_lib_path.?}); - } else { - logger.warn("Zig executable path not specified in zls.json and could not be found in PATH", .{}); - } - - if (config.zig_lib_path == null) { - logger.warn("Zig standard library path not specified in zls.json and could not be resolved from the zig executable", .{}); - } - - if (config.builtin_path == null and config.zig_exe_path != null and builtin_creation_dir != null) blk: { - const result = try std.ChildProcess.exec(.{ - .allocator = allocator, - .argv = &.{ - config.zig_exe_path.?, - "build-exe", - "--show-builtin", - }, - .max_output_bytes = 1024 * 1024 * 50, - }); - defer allocator.free(result.stdout); - defer allocator.free(result.stderr); - - var d = try std.fs.cwd().openDir(builtin_creation_dir.?, .{}); - defer d.close(); - - const f = d.createFile("builtin.zig", .{}) catch |err| switch (err) { - error.AccessDenied => break :blk, - else => |e| return e, - }; - defer f.close(); - - try f.writer().writeAll(result.stdout); - - config.builtin_path = try std.fs.path.join(allocator, &.{ builtin_creation_dir.?, "builtin.zig" }); - } - - if (null == config.global_cache_path) { - const cache_dir_path = (try known_folders.getPath(allocator, .cache)) orelse { - logger.warn("Known-folders could not fetch the cache path", .{}); - return; - }; - defer allocator.free(cache_dir_path); - - config.global_cache_path = try std.fs.path.resolve(allocator, &[_][]const u8{ cache_dir_path, "zls" }); - - std.fs.cwd().makePath(config.global_cache_path.?) catch |err| switch (err) { - error.PathAlreadyExists => {}, - else => return err, - }; - } - - if (null == config.build_runner_path) { - config.build_runner_path = try std.fs.path.resolve(allocator, &[_][]const u8{ config.global_cache_path.?, "build_runner.zig" }); - - const file = try std.fs.createFileAbsolute(config.build_runner_path.?, .{}); - defer file.close(); - - try file.writeAll(@embedFile("special/build_runner.zig")); - } -} - -pub const Env = struct { - zig_exe: []const u8, - lib_dir: ?[]const u8, - std_dir: []const u8, - global_cache_dir: []const u8, - version: []const u8, - target: ?[]const u8 = null, -}; - -/// result has to be freed with `std.json.parseFree` -pub fn getZigEnv(allocator: std.mem.Allocator, zig_exe_path: []const u8) ?Env { - const zig_env_result = std.ChildProcess.exec(.{ - .allocator = allocator, - .argv = &[_][]const u8{ zig_exe_path, "env" }, - }) catch { - logger.err("Failed to execute zig env", .{}); - return null; - }; - - defer { - allocator.free(zig_env_result.stdout); - allocator.free(zig_env_result.stderr); - } - - switch (zig_env_result.term) { - .Exited => |code| { - if (code != 0) { - logger.err("zig env failed with error_code: {}", .{code}); - return null; - } - }, - else => logger.err("zig env invocation failed", .{}), - } - - var token_stream = std.json.TokenStream.init(zig_env_result.stdout); - return std.json.parse( - Env, - &token_stream, - .{ - .allocator = allocator, - .ignore_unknown_fields = true, - }, - ) catch { - logger.err("Failed to parse zig env JSON result", .{}); - return null; - }; -} - -pub const Configuration = Config.getConfigurationType(); -pub const DidChangeConfigurationParams = struct { - settings: ?Configuration, -}; - -// returns a Struct which is the same as `Config` except that every field is optional. -fn getConfigurationType() type { - var config_info: std.builtin.Type = @typeInfo(Config); - var fields: [config_info.Struct.fields.len]std.builtin.Type.StructField = undefined; - for (config_info.Struct.fields) |field, i| { - fields[i] = field; - if (@typeInfo(field.field_type) != .Optional) { - fields[i].field_type = @Type(std.builtin.Type{ - .Optional = .{ .child = field.field_type }, - }); - } - } - config_info.Struct.fields = fields[0..]; - config_info.Struct.decls = &.{}; - return @Type(config_info); -} +// DO NOT EDIT diff --git a/src/Server.zig b/src/Server.zig index 7aabb99..6c58be4 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -4,6 +4,7 @@ const std = @import("std"); const zig_builtin = @import("builtin"); const build_options = @import("build_options"); const Config = @import("Config.zig"); +const configuration = @import("configuration.zig"); const DocumentStore = @import("DocumentStore.zig"); const requests = @import("requests.zig"); const types = @import("types.zig"); @@ -1570,6 +1571,22 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req: const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); + if (req.params.clientInfo) |clientInfo| { + std.log.info("client is '{s}-{s}'", .{ clientInfo.name, clientInfo.version orelse "" }); + + if (std.mem.eql(u8, clientInfo.name, "Sublime Text LSP")) blk: { + server.config.max_detail_length = 256; + + const version_str = clientInfo.version orelse break :blk; + const version = std.SemanticVersion.parse(version_str) catch break :blk; + // this indicates a LSP version for sublime text 3 + // this check can be made more precise if the version that fixed this issue is known + if (version.major == 0) { + server.config.include_at_in_builtins = true; + } + } + } + if (req.params.capabilities.general) |general| { var supports_utf8 = false; var supports_utf16 = false; @@ -1717,8 +1734,8 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req: // TODO avoid having to call getZigEnv twice // once in init and here - const env = Config.getZigEnv(server.allocator, server.config.zig_exe_path.?) orelse return; - defer std.json.parseFree(Config.Env, env, .{ .allocator = server.allocator }); + const env = configuration.getZigEnv(server.allocator, server.config.zig_exe_path.?) orelse return; + defer std.json.parseFree(configuration.Env, env, .{ .allocator = server.allocator }); const zig_exe_version = std.SemanticVersion.parse(env.version) catch return; @@ -2197,16 +2214,16 @@ fn formattingHandler(server: *Server, writer: anytype, id: types.RequestId, req: ); } -fn didChangeConfigurationHandler(server: *Server, writer: anytype, id: types.RequestId, req: Config.DidChangeConfigurationParams) !void { +fn didChangeConfigurationHandler(server: *Server, writer: anytype, id: types.RequestId, req: configuration.DidChangeConfigurationParams) !void { const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); _ = id; // NOTE: VS Code seems to always respond with null - if (req.settings) |configuration| { - inline for (std.meta.fields(Config.Configuration)) |field| { - if (@field(configuration, field.name)) |value| { + if (req.settings) |cfg| { + inline for (std.meta.fields(configuration.Configuration)) |field| { + if (@field(cfg, field.name)) |value| { blk: { if (@TypeOf(value) == []const u8) { if (value.len == 0) { @@ -2219,7 +2236,7 @@ fn didChangeConfigurationHandler(server: *Server, writer: anytype, id: types.Req } } - try server.config.configChanged(server.allocator, null); + try configuration.configChanged(server.config, server.allocator, null); } else if (server.client_capabilities.supports_configuration) { try server.requestConfiguration(writer); } @@ -2838,7 +2855,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void } } - try server.config.configChanged(server.allocator, null); + try configuration.configChanged(server.config, server.allocator, null); return; } @@ -2901,7 +2918,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void .{ "textDocument/references", requests.References, referencesHandler }, .{ "textDocument/documentHighlight", requests.DocumentHighlight, documentHighlightHandler }, .{ "textDocument/codeAction", requests.CodeAction, codeActionHandler }, - .{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler }, + .{ "workspace/didChangeConfiguration", configuration.DidChangeConfigurationParams, didChangeConfigurationHandler }, .{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler }, .{ "textDocument/selectionRange", requests.SelectionRange, selectionRangeHandler }, }; @@ -2998,7 +3015,7 @@ pub fn init( // see: https://github.com/zigtools/zls/issues/536 analysis.init(allocator); - try config.configChanged(allocator, config_path); + try configuration.configChanged(config, allocator, config_path); var document_store = DocumentStore{ .allocator = allocator, diff --git a/src/config_gen/config.json b/src/config_gen/config.json new file mode 100644 index 0000000..e637338 --- /dev/null +++ b/src/config_gen/config.json @@ -0,0 +1,158 @@ +{ + "options": [ + { + "name": "enable_snippets", + "description": "Enables snippet completions when the client also supports them", + "type": "bool", + "default": "false", + "setup_question": "Do you want to enable snippets?" + }, + { + "name": "enable_ast_check_diagnostics", + "description": "Whether to enable ast-check diagnostics", + "type": "bool", + "default": "true", + "setup_question": "Do you want to enable ast-check diagnostics?" + }, + { + "name": "enable_autofix", + "description": "Whether to automatically fix errors on save. Currently supports adding and removing discards.", + "type": "bool", + "default": "false", + "setup_question": "Do you want to zls to automatically try to fix errors on save? (supports adding & removing discards)" + }, + { + "name": "enable_import_embedfile_argument_completions", + "description": "Whether to enable import/embedFile argument completions", + "type": "bool", + "default": "false", + "setup_question": "Do you want to enable @import/@embedFile argument path completion?" + }, + { + "name": "enable_semantic_tokens", + "description": "Enables semantic token support when the client also supports it", + "type": "bool", + "default": "true", + "setup_question": "Do you want to enable semantic highlighting?" + }, + { + "name": "enable_inlay_hints", + "description": "Enables inlay hint support when the client also supports it", + "type": "bool", + "default": "false", + "setup_question": "Do you want to enable inlay hints?" + }, + { + "name": "inlay_hints_show_builtin", + "description": "Enable inlay hints for builtin functions", + "type": "bool", + "default": "true", + "setup_question": null + }, + { + "name": "inlay_hints_exclude_single_argument", + "description": "Don't show inlay hints for single argument calls", + "type": "bool", + "default": "true", + "setup_question": null + }, + { + "name": "inlay_hints_hide_redundant_param_names", + "description": "Hides inlay hints when parameter name matches the identifier (e.g. foo: foo)", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "inlay_hints_hide_redundant_param_names_last_token", + "description": "Hides inlay hints when parameter name matches the last token of a parameter node (e.g. foo: bar.foo, foo: &foo)", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "operator_completions", + "description": "Enables `*` and `?` operators in completion lists", + "type": "bool", + "default": "true", + "setup_question": "Do you want to enable .* and .? completions?" + }, + { + "name": "warn_style", + "description": "Enables warnings for style guideline mismatches", + "type": "bool", + "default": "false", + "setup_question": "Do you want to enable style warnings?" + }, + { + "name": "highlight_global_var_declarations", + "description": "Whether to highlight global var declarations", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "use_comptime_interpreter", + "description": "Whether to use the comptime interpreter", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "include_at_in_builtins", + "description": "Whether the @ sign should be part of the completion of builtins", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "skip_std_references", + "description": "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", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "max_detail_length", + "description": "The detail field of completions is truncated to be no longer than this (in bytes)", + "type": "usize", + "default": "1048576", + "setup_question": null + }, + { + "name": "builtin_path", + "description": "Path to 'builtin;' useful for debugging, automatically set if let null", + "type": "?[]const u8", + "default": "null", + "setup_question": null + }, + { + "name": "zig_lib_path", + "description": "Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports", + "type": "?[]const u8", + "default": "null", + "setup_question": null + }, + { + "name": "zig_exe_path", + "description": "Zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided", + "type": "?[]const u8", + "default": "null", + "setup_question": null + }, + { + "name": "build_runner_path", + "description": "Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig`", + "type": "?[]const u8", + "default": "null", + "setup_question": null + }, + { + "name": "global_cache_path", + "description": "Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls`", + "type": "?[]const u8", + "default": "null", + "setup_question": null + } + ] +} \ No newline at end of file diff --git a/src/config_gen/config_gen.zig b/src/config_gen/config_gen.zig new file mode 100644 index 0000000..0952df1 --- /dev/null +++ b/src/config_gen/config_gen.zig @@ -0,0 +1,239 @@ +const std = @import("std"); + +const ConfigOption = struct { + /// Name of config option + name: []const u8, + /// (used in doc comments & schema.json) + description: []const u8, + /// zig type in string form. e.g "u32", "[]const u8", "?usize" + type: []const u8, + /// used in Config.zig as the default initializer + default: []const u8, + /// If set, this option can be configured through `zls --config` + /// currently unused but could laer be used to automatically generate queries for setup.zig + setup_question: ?[]const u8, +}; + +const Config = struct { + options: []ConfigOption, +}; + +const Schema = struct { + @"$schema": []const u8 = "http://json-schema.org/schema", + title: []const u8 = "ZLS Config", + description: []const u8 = "Configuration file for the zig language server (ZLS)", + type: []const u8 = "object", + properties: std.StringArrayHashMap(SchemaEntry), +}; + +const SchemaEntry = struct { + description: []const u8, + type: []const u8, + default: []const u8, +}; + +fn zigTypeToTypescript(ty: []const u8) ![]const u8 { + return if (std.mem.eql(u8, ty, "?[]const u8")) + "string" + else if (std.mem.eql(u8, ty, "bool")) + "boolean" + else if (std.mem.eql(u8, ty, "usize")) + "integer" + else + error.UnsupportedType; +} + +fn generateConfigFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void { + _ = allocator; + + const config_file = try std.fs.openFileAbsolute(path, .{ + .mode = .write_only, + }); + defer config_file.close(); + + var buff_out = std.io.bufferedWriter(config_file.writer()); + + _ = try buff_out.write( + \\//! DO NOT EDIT + \\//! Configuration options for zls. + \\//! If you want to add a config option edit + \\//! src/config_gen/config.zig and run `zig build gen` + \\//! GENERATED BY src/config_gen/config_gen.zig + \\ + ); + + for (config.options) |option| { + try buff_out.writer().print( + \\ + \\/// {s} + \\{s}: {s} = {s}, + \\ + , .{ + std.mem.trim(u8, option.description, " \t\n\r"), + std.mem.trim(u8, option.name, " \t\n\r"), + std.mem.trim(u8, option.type, " \t\n\r"), + std.mem.trim(u8, option.default, " \t\n\r"), + }); + } + + _ = try buff_out.write( + \\ + \\// DO NOT EDIT + \\ + ); + + try buff_out.flush(); +} + +fn generateSchemaFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void { + const schema_file = try std.fs.openFileAbsolute(path, .{ + .mode = .write_only, + }); + defer schema_file.close(); + + var buff_out = std.io.bufferedWriter(schema_file.writer()); + + var properties = std.StringArrayHashMapUnmanaged(SchemaEntry){}; + defer properties.deinit(allocator); + try properties.ensureTotalCapacity(allocator, config.options.len); + + for (config.options) |option| { + properties.putAssumeCapacityNoClobber(option.name, .{ + .description = option.description, + .type = try zigTypeToTypescript(option.type), + .default = option.default, + }); + } + + _ = try buff_out.write( + \\{ + \\ "$schema": "http://json-schema.org/schema", + \\ "title": "ZLS Config", + \\ "description": "Configuration file for the zig language server (ZLS)", + \\ "type": "object", + \\ "properties": + ); + + try serializeObjectMap(properties, .{ + .whitespace = .{ + .indent_level = 1, + }, + }, buff_out.writer()); + + _ = try buff_out.write("\n}\n"); + try buff_out.flush(); +} + +fn updateREADMEFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void { + var readme_file = try std.fs.openFileAbsolute(path, .{ .mode = .read_write }); + defer readme_file.close(); + + var readme = std.ArrayListUnmanaged(u8){ + .items = try readme_file.readToEndAlloc(allocator, std.math.maxInt(usize)), + }; + defer readme.deinit(allocator); + + const start_indicator = ""; + const end_indicator = ""; + + const start = start_indicator.len + (std.mem.indexOf(u8, readme.items, start_indicator) orelse return error.SectionNotFound); + const end = std.mem.indexOfPos(u8, readme.items, start, end_indicator) orelse return error.SectionNotFound; + + var new_readme = std.ArrayListUnmanaged(u8){}; + defer new_readme.deinit(allocator); + var writer = new_readme.writer(allocator); + + try writer.writeAll( + \\ + \\| Option | Type | Default value | What it Does | + \\| --- | --- | --- | --- | + \\ + ); + + for (config.options) |option| { + try writer.print( + \\| `{s}` | `{s}` | `{s}` | {s} | + \\ + , .{ + std.mem.trim(u8, option.name, " \t\n\r"), + std.mem.trim(u8, option.type, " \t\n\r"), + std.mem.trim(u8, option.default, " \t\n\r"), + std.mem.trim(u8, option.description, " \t\n\r"), + }); + } + + try readme.replaceRange(allocator, start, end - start, new_readme.items); + + try readme_file.seekTo(0); + try readme_file.writeAll(readme.items); +} + +pub fn main() !void { + var arg_it = std.process.args(); + + _ = arg_it.next() orelse @panic(""); + const config_path = arg_it.next() orelse @panic("first argument must be path to Config.zig"); + const schema_path = arg_it.next() orelse @panic("second argument must be path to schema.json"); + const readme_path = arg_it.next() orelse @panic("third argument must be path to README.md"); + + var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa = general_purpose_allocator.allocator(); + + const parse_options = std.json.ParseOptions{ + .allocator = gpa, + }; + var token_stream = std.json.TokenStream.init(@embedFile("config.json")); + const config = try std.json.parse(Config, &token_stream, parse_options); + defer std.json.parseFree(Config, config, parse_options); + + try generateConfigFile(gpa, config, config_path); + try generateSchemaFile(gpa, config, schema_path); + try updateREADMEFile(gpa, config, readme_path); + + std.log.warn( + \\ If you have added a new configuration option and it should be configuration through the config wizard, then edit src/setup.zig + , .{}); + + std.log.info( + \\ Changing configuration options may also require editing the `package.json` from zls-vscode at https://github.com/zigtools/zls-vscode/blob/master/package.json + , .{}); +} + +fn serializeObjectMap( + value: anytype, + options: std.json.StringifyOptions, + out_stream: anytype, +) @TypeOf(out_stream).Error!void { + try out_stream.writeByte('{'); + var field_output = false; + var child_options = options; + if (child_options.whitespace) |*child_whitespace| { + child_whitespace.indent_level += 1; + } + var it = value.iterator(); + while (it.next()) |entry| { + if (!field_output) { + field_output = true; + } else { + try out_stream.writeByte(','); + } + if (child_options.whitespace) |child_whitespace| { + try child_whitespace.outputIndent(out_stream); + } + + try std.json.stringify(entry.key_ptr.*, options, out_stream); + try out_stream.writeByte(':'); + if (child_options.whitespace) |child_whitespace| { + if (child_whitespace.separator) { + try out_stream.writeByte(' '); + } + } + try std.json.stringify(entry.value_ptr.*, child_options, out_stream); + } + if (field_output) { + if (options.whitespace) |whitespace| { + try whitespace.outputIndent(out_stream); + } + } + try out_stream.writeByte('}'); +} diff --git a/src/configuration.zig b/src/configuration.zig new file mode 100644 index 0000000..9204e2b --- /dev/null +++ b/src/configuration.zig @@ -0,0 +1,210 @@ +const std = @import("std"); + +const setup = @import("setup.zig"); +const tracy = @import("tracy.zig"); +const known_folders = @import("known-folders"); + +const Config = @import("Config.zig"); + +const logger = std.log.scoped(.config); + +pub fn loadFromFile(allocator: std.mem.Allocator, file_path: []const u8) ?Config { + const tracy_zone = tracy.trace(@src()); + defer tracy_zone.end(); + + var file = std.fs.cwd().openFile(file_path, .{}) catch |err| { + if (err != error.FileNotFound) + logger.warn("Error while reading configuration file: {}", .{err}); + return null; + }; + + defer file.close(); + + const file_buf = file.readToEndAlloc(allocator, 0x1000000) catch return null; + defer allocator.free(file_buf); + @setEvalBranchQuota(10000); + + var token_stream = std.json.TokenStream.init(file_buf); + const parse_options = std.json.ParseOptions{ .allocator = allocator, .ignore_unknown_fields = true }; + + // TODO: Better errors? Doesn't seem like std.json can provide us positions or context. + var config = std.json.parse(Config, &token_stream, parse_options) catch |err| { + logger.warn("Error while parsing configuration file: {}", .{err}); + return null; + }; + + if (config.zig_lib_path) |zig_lib_path| { + if (!std.fs.path.isAbsolute(zig_lib_path)) { + logger.warn("zig library path is not absolute, defaulting to null.", .{}); + allocator.free(zig_lib_path); + config.zig_lib_path = null; + } + } + + return config; +} + +pub fn loadFromFolder(allocator: std.mem.Allocator, folder_path: []const u8) ?Config { + const tracy_zone = tracy.trace(@src()); + defer tracy_zone.end(); + + const full_path = std.fs.path.resolve(allocator, &.{ folder_path, "zls.json" }) catch return null; + defer allocator.free(full_path); + return loadFromFile(allocator, full_path); +} + +/// Invoke this once all config values have been changed. +pub fn configChanged(config: *Config, allocator: std.mem.Allocator, builtin_creation_dir: ?[]const u8) !void { + // Find the zig executable in PATH + find_zig: { + if (config.zig_exe_path) |exe_path| { + if (std.fs.path.isAbsolute(exe_path)) not_valid: { + std.fs.cwd().access(exe_path, .{}) catch break :not_valid; + break :find_zig; + } + logger.debug("zig path `{s}` is not absolute, will look in path", .{exe_path}); + allocator.free(exe_path); + } + config.zig_exe_path = try setup.findZig(allocator); + } + + if (config.zig_exe_path) |exe_path| blk: { + logger.info("Using zig executable {s}", .{exe_path}); + + if (config.zig_lib_path != null) break :blk; + + var env = getZigEnv(allocator, exe_path) orelse break :blk; + defer std.json.parseFree(Env, env, .{ .allocator = allocator }); + + // Make sure the path is absolute + config.zig_lib_path = try std.fs.realpathAlloc(allocator, env.lib_dir.?); + logger.info("Using zig lib path '{s}'", .{config.zig_lib_path.?}); + } else { + logger.warn("Zig executable path not specified in zls.json and could not be found in PATH", .{}); + } + + if (config.zig_lib_path == null) { + logger.warn("Zig standard library path not specified in zls.json and could not be resolved from the zig executable", .{}); + } + + if (config.builtin_path == null and config.zig_exe_path != null and builtin_creation_dir != null) blk: { + const result = try std.ChildProcess.exec(.{ + .allocator = allocator, + .argv = &.{ + config.zig_exe_path.?, + "build-exe", + "--show-builtin", + }, + .max_output_bytes = 1024 * 1024 * 50, + }); + defer allocator.free(result.stdout); + defer allocator.free(result.stderr); + + var d = try std.fs.cwd().openDir(builtin_creation_dir.?, .{}); + defer d.close(); + + const f = d.createFile("builtin.zig", .{}) catch |err| switch (err) { + error.AccessDenied => break :blk, + else => |e| return e, + }; + defer f.close(); + + try f.writer().writeAll(result.stdout); + + config.builtin_path = try std.fs.path.join(allocator, &.{ builtin_creation_dir.?, "builtin.zig" }); + } + + if (null == config.global_cache_path) { + const cache_dir_path = (try known_folders.getPath(allocator, .cache)) orelse { + logger.warn("Known-folders could not fetch the cache path", .{}); + return; + }; + defer allocator.free(cache_dir_path); + + config.global_cache_path = try std.fs.path.resolve(allocator, &[_][]const u8{ cache_dir_path, "zls" }); + + std.fs.cwd().makePath(config.global_cache_path.?) catch |err| switch (err) { + error.PathAlreadyExists => {}, + else => return err, + }; + } + + if (null == config.build_runner_path) { + config.build_runner_path = try std.fs.path.resolve(allocator, &[_][]const u8{ config.global_cache_path.?, "build_runner.zig" }); + + const file = try std.fs.createFileAbsolute(config.build_runner_path.?, .{}); + defer file.close(); + + try file.writeAll(@embedFile("special/build_runner.zig")); + } +} + +pub const Env = struct { + zig_exe: []const u8, + lib_dir: ?[]const u8, + std_dir: []const u8, + global_cache_dir: []const u8, + version: []const u8, + target: ?[]const u8 = null, +}; + +/// result has to be freed with `std.json.parseFree` +pub fn getZigEnv(allocator: std.mem.Allocator, zig_exe_path: []const u8) ?Env { + const zig_env_result = std.ChildProcess.exec(.{ + .allocator = allocator, + .argv = &[_][]const u8{ zig_exe_path, "env" }, + }) catch { + logger.err("Failed to execute zig env", .{}); + return null; + }; + + defer { + allocator.free(zig_env_result.stdout); + allocator.free(zig_env_result.stderr); + } + + switch (zig_env_result.term) { + .Exited => |code| { + if (code != 0) { + logger.err("zig env failed with error_code: {}", .{code}); + return null; + } + }, + else => logger.err("zig env invocation failed", .{}), + } + + var token_stream = std.json.TokenStream.init(zig_env_result.stdout); + return std.json.parse( + Env, + &token_stream, + .{ + .allocator = allocator, + .ignore_unknown_fields = true, + }, + ) catch { + logger.err("Failed to parse zig env JSON result", .{}); + return null; + }; +} + +pub const Configuration = getConfigurationType(); +pub const DidChangeConfigurationParams = struct { + settings: ?Configuration, +}; + +// returns a Struct which is the same as `Config` except that every field is optional. +fn getConfigurationType() type { + var config_info: std.builtin.Type = @typeInfo(Config); + var fields: [config_info.Struct.fields.len]std.builtin.Type.StructField = undefined; + for (config_info.Struct.fields) |field, i| { + fields[i] = field; + if (@typeInfo(field.field_type) != .Optional) { + fields[i].field_type = @Type(std.builtin.Type{ + .Optional = .{ .child = field.field_type }, + }); + } + } + config_info.Struct.fields = fields[0..]; + config_info.Struct.decls = &.{}; + return @Type(config_info); +} diff --git a/src/main.zig b/src/main.zig index 4f74bf8..03776c4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,6 +4,7 @@ const build_options = @import("build_options"); const tracy = @import("tracy.zig"); const known_folders = @import("known-folders"); const Config = @import("Config.zig"); +const configuration = @import("configuration.zig"); const Server = @import("Server.zig"); const setup = @import("setup.zig"); const readRequestHeader = @import("header.zig").readRequestHeader; @@ -65,7 +66,7 @@ fn getConfig( free_old_config_path: bool, ) !ConfigWithPath { if (config_path) |path| { - if (Config.loadFromFile(allocator, path)) |conf| { + if (configuration.loadFromFile(allocator, path)) |conf| { return ConfigWithPath{ .config = conf, .config_path = path, @@ -82,7 +83,7 @@ fn getConfig( } if (try known_folders.getPath(allocator, .local_configuration)) |path| { - if (Config.loadFromFolder(allocator, path)) |conf| { + if (configuration.loadFromFolder(allocator, path)) |conf| { return ConfigWithPath{ .config = conf, .config_path = path, @@ -92,7 +93,7 @@ fn getConfig( } if (try known_folders.getPath(allocator, .global_configuration)) |path| { - if (Config.loadFromFolder(allocator, path)) |conf| { + if (configuration.loadFromFolder(allocator, path)) |conf| { return ConfigWithPath{ .config = conf, .config_path = path, diff --git a/src/requests.zig b/src/requests.zig index 466a7bd..b728161 100644 --- a/src/requests.zig +++ b/src/requests.zig @@ -178,7 +178,13 @@ pub const Initialize = struct { }, }; + pub const ClientInfo = struct { + name: []const u8, + version: ?[]const u8, + }; + params: struct { + clientInfo: ?ClientInfo, capabilities: ClientCapabilities, workspaceFolders: ?[]const types.WorkspaceFolder, }, diff --git a/src/setup.zig b/src/setup.zig index c32861e..6085a03 100644 --- a/src/setup.zig +++ b/src/setup.zig @@ -90,19 +90,11 @@ pub fn askSelectOne(prompt: []const u8, comptime Options: type) !Options { } } -fn print(comptime fmt: []const u8, args: anytype) void { - const stdout = std.io.getStdOut().writer(); - stdout.print(fmt, args) catch @panic("Could not write to stdout"); -} - -fn write(text: []const u8) void { - const stdout = std.io.getStdOut().writer(); - stdout.writeAll(text) catch @panic("Could not write to stdout"); -} - pub fn wizard(allocator: std.mem.Allocator) !void { @setEvalBranchQuota(2500); - write( + const stdout = std.io.getStdOut().writer(); + + try stdout.writeAll( \\Welcome to the ZLS configuration wizard! \\ * \\ |\ @@ -127,7 +119,7 @@ pub fn wizard(allocator: std.mem.Allocator) !void { }; if (global_path == null and local_path == null) { - write("Could not open a global or local config directory.\n"); + try stdout.writeAll("Could not open a global or local config directory.\n"); return; } var config_path: []const u8 = undefined; @@ -137,17 +129,17 @@ pub fn wizard(allocator: std.mem.Allocator) !void { if (local_path) |p| { config_path = p; } else { - write("Could not find a local config directory.\n"); + try stdout.writeAll("Could not find a local config directory.\n"); return; } } var dir = std.fs.cwd().openDir(config_path, .{}) catch |err| { - print("Could not open {s}: {}.\n", .{ config_path, err }); + try stdout.print("Could not open {s}: {}.\n", .{ config_path, err }); return; }; defer dir.close(); var file = dir.createFile("zls.json", .{}) catch |err| { - print("Could not create {s}/zls.json: {}.\n", .{ config_path, err }); + try stdout.print("Could not create {s}/zls.json: {}.\n", .{ config_path, err }); return; }; defer file.close(); @@ -157,9 +149,9 @@ pub fn wizard(allocator: std.mem.Allocator) !void { defer if (zig_exe_path) |p| allocator.free(p); if (zig_exe_path) |path| { - print("Found zig executable '{s}' in PATH.\n", .{path}); + try stdout.print("Found zig executable '{s}' in PATH.\n", .{path}); } else { - write("Could not find 'zig' in PATH\n"); + try stdout.writeAll("Could not find 'zig' in PATH\n"); zig_exe_path = try askString(allocator, if (builtin.os.tag == .windows) \\What is the path to the 'zig' executable you would like to use? \\Note that due to a bug in zig (https://github.com/ziglang/zig/issues/6044), @@ -168,7 +160,6 @@ pub fn wizard(allocator: std.mem.Allocator) !void { "What is the path to the 'zig' executable you would like to use?", std.fs.MAX_PATH_BYTES); } - const editor = try askSelectOne("Which code editor do you use?", enum { VSCode, Sublime, Kate, Neovim, Vim8, Emacs, Doom, Spacemacs, Helix, Other }); const snippets = try askBool("Do you want to enable snippets?"); const ast_check = try askBool("Do you want to enable ast-check diagnostics?"); const autofix = try askBool("Do you want to zls to automatically try to fix errors on save? (supports adding & removing discards)"); @@ -177,15 +168,6 @@ pub fn wizard(allocator: std.mem.Allocator) !void { const semantic_tokens = try askBool("Do you want to enable semantic highlighting?"); const inlay_hints = try askBool("Do you want to enable inlay hints?"); const operator_completions = try askBool("Do you want to enable .* and .? completions?"); - const include_at_in_builtins = switch (editor) { - .Sublime => !try askBool("Are you using a Sublime Text version > 4000?"), - .VSCode, .Kate, .Neovim, .Vim8, .Emacs, .Doom, .Spacemacs, .Helix => false, - else => try askBool("Should the @ sign be included in completions of builtin functions?\nChange this later if `@inc` completes to `include` or `@@include`"), - }; - const max_detail_length: usize = switch (editor) { - .Sublime => 256, - else => 1024 * 1024, - }; std.debug.print("Writing config to {s}/zls.json ... ", .{config_path}); @@ -200,144 +182,18 @@ pub fn wizard(allocator: std.mem.Allocator) !void { .enable_semantic_tokens = semantic_tokens, .enable_inlay_hints = inlay_hints, .operator_completions = operator_completions, - .include_at_in_builtins = include_at_in_builtins, - .max_detail_length = max_detail_length, }, .{ .whitespace = .{}, }, out); - write("successful.\n\n\n\n"); - - // Keep synced with README.md - switch (editor) { - .VSCode => { - write( - \\To use ZLS in Visual Studio Code, install the 'ZLS for VSCode' extension from - \\'https://github.com/zigtools/zls-vscode/releases' or via the extensions menu. - \\ZLS will automatically be installed if it is not found in your PATH - ); - }, - .Sublime => { - write( - \\To use ZLS in Sublime, install the `LSP` package from - \\https://github.com/sublimelsp/LSP/releases or via Package Control. - \\Then, add the following snippet to LSP's user settings: - \\ - \\For Sublime Text 3: - \\ - \\{ - \\ "clients": { - \\ "zig": { - \\ "command": ["zls"], - \\ "enabled": true, - \\ "languageId": "zig", - \\ "scopes": ["source.zig"], - \\ "syntaxes": ["Packages/Zig Language/Syntaxes/Zig.tmLanguage"] - \\ } - \\ } - \\} - \\ - \\For Sublime Text 4: - \\ - \\{ - \\ "clients": { - \\ "zig": { - \\ "command": ["zls"], - \\ "enabled": true, - \\ "selector": "source.zig" - \\ } - \\ } - \\} - ); - }, - .Kate => { - write( - \\To use ZLS in Kate, enable `LSP client` plugin in Kate settings. - \\Then, add the following snippet to `LSP client's` user settings: - \\(or paste it in `LSP client's` GUI settings) - \\ - \\{ - \\ "servers": { - \\ "zig": { - \\ "command": ["zls"], - \\ "url": "https://github.com/zigtools/zls", - \\ "highlightingModeRegex": "^Zig$" - \\ } - \\ } - \\} - ); - }, - .Neovim, .Vim8 => { - write( - \\To use ZLS in Neovim/Vim8, we recommend using CoC engine. - \\You can get it from https://github.com/neoclide/coc.nvim. - \\Then, simply issue cmd from Neovim/Vim8 `:CocConfig`, and add this to your CoC config: - \\ - \\{ - \\ "languageserver": { - \\ "zls" : { - \\ "command": "command_or_path_to_zls", - \\ "filetypes": ["zig"] - \\ } - \\ } - \\} - ); - }, - .Emacs => { - write( - \\To use ZLS in Emacs, install lsp-mode (https://github.com/emacs-lsp/lsp-mode) from melpa. - \\Zig mode (https://github.com/ziglang/zig-mode) is also useful! - \\Then, add the following to your emacs config: - \\ - \\(require 'lsp-mode) - \\(setq lsp-zig-zls-executable "") - ); - }, - .Doom => { - write( - \\To use ZLS in Doom Emacs, enable the lsp module - \\And install the `zig-mode` (https://github.com/ziglang/zig-mode) - \\package by adding `(package! zig-mode)` to your packages.el file. - \\ - \\(use-package! zig-mode - \\ :hook ((zig-mode . lsp-deferred)) - \\ :custom (zig-format-on-save nil) - \\ :config - \\ (after! lsp-mode - \\ (add-to-list 'lsp-language-id-configuration '(zig-mode . "zig")) - \\ (lsp-register-client - \\ (make-lsp-client - \\ :new-connection (lsp-stdio-connection "") - \\ :major-modes '(zig-mode) - \\ :server-id 'zls)))) - ); - }, - .Spacemacs => { - write( - \\To use ZLS in Spacemacs, add the `lsp` and `zig` layers - \\to `dotspacemacs-configuration-layers` in your .spacemacs file. - \\Then, if you don't have `zls` in your PATH, add the following to - \\`dotspacemacs/user-config` in your .spacemacs file: - \\ - \\(setq lsp-zig-zls-executable "") - ); - }, - .Helix => { - write( - \\Helix has out of the box support for ZLS - \\Make sure you have added ZLS to your PATH - \\run hx --health to check if helix has found it. - ); - }, - .Other => { - write( - \\We might not *officially* support your editor, but you can definitely still use ZLS! - \\Simply configure your editor for use with language servers and point it to the ZLS executable! - ); - }, - } - - write("\n\nThank you for choosing ZLS!\n"); + try stdout.writeAll( + \\successful. + \\ + \\You can find information on how to setup zls for your editor on zigtools.github.io/install-zls/ + \\ + \\Thank you for choosing ZLS! + \\ + ); } pub fn findZig(allocator: std.mem.Allocator) !?[]const u8 { From 974bdff6b35bca0ab43b498e81f36a63ae8b8c9d Mon Sep 17 00:00:00 2001 From: Lee Cannon Date: Mon, 12 Dec 2022 00:13:13 +0000 Subject: [PATCH 05/21] fix missed reference on windows --- src/uri.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uri.zig b/src/uri.zig index a1064d8..41bc5a4 100644 --- a/src/uri.zig +++ b/src/uri.zig @@ -41,7 +41,7 @@ pub fn fromPath(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { // On windows, we need to lowercase the drive name. if (builtin.os.tag == .windows) { if (buf.items.len > prefix.len + 1 and - std.ascii.isAlpha(buf.items[prefix.len]) and + std.ascii.isAlphanumeric(buf.items[prefix.len]) and std.mem.startsWith(u8, buf.items[prefix.len + 1 ..], "%3A")) { buf.items[prefix.len] = std.ascii.toLower(buf.items[prefix.len]); From 0ab34abc0f2af94ff269488e94dad62e2bd84431 Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Tue, 13 Dec 2022 22:07:36 -0500 Subject: [PATCH 06/21] Bug stream fixes (#818) * Fix glaring inlay hint issue; thanks for the report Nameless * Fix label references; closes #728 --- src/Server.zig | 10 ++++------ src/analysis.zig | 23 ++++++++++++++++------- src/inlay_hints.zig | 29 +++++++++++++++++------------ src/references.zig | 4 ++-- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/Server.zig b/src/Server.zig index 9ce0638..61d2a84 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -876,7 +876,7 @@ fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle) error{OutO .array_payload => |payload| handle.tree.tokenSlice(payload.identifier), .array_index => |payload| handle.tree.tokenSlice(payload), .switch_payload => |payload| tree.tokenSlice(payload.node), - .label_decl => |label_decl| tree.tokenSlice(label_decl), + .label_decl => |label_decl| tree.tokenSlice(label_decl.label), }; var bound_type_params = analysis.BoundTypeParams{}; @@ -1194,9 +1194,9 @@ fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.Decl }, .label_decl => |label_decl| { try context.completions.append(allocator, .{ - .label = tree.tokenSlice(label_decl), + .label = tree.tokenSlice(label_decl.label), .kind = .Variable, - .insertText = tree.tokenSlice(label_decl), + .insertText = tree.tokenSlice(label_decl.label), .insertTextFormat = .PlainText, }); }, @@ -2313,9 +2313,7 @@ fn generalReferencesHandler(server: *Server, writer: anytype, id: types.RequestI }; const locations = if (pos_context == .label) - // FIXME https://github.com/zigtools/zls/issues/728 - // try references.labelReferences(allocator, decl, server.offset_encoding, include_decl) - std.ArrayListUnmanaged(types.Location){} + try references.labelReferences(allocator, decl, server.offset_encoding, include_decl) else try references.symbolReferences( &server.arena, diff --git a/src/analysis.zig b/src/analysis.zig index 1e32ae4..65d287a 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -795,15 +795,15 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl return null; }; - const ti = val.@"type".getTypeInfo(); - if (ti != .@"type") { + 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" } }, + .data = .{ .@"comptime" = .{ .interpreter = interpreter, .type = val.value_data.type } }, .is_type_val = true, }, .handle = node_handle.handle, @@ -1956,7 +1956,10 @@ pub const Declaration = union(enum) { switch_expr: Ast.Node.Index, items: []const Ast.Node.Index, }, - label_decl: Ast.TokenIndex, + label_decl: struct { + label: Ast.TokenIndex, + block: Ast.Node.Index, + }, }; pub const DeclWithHandle = struct { @@ -1972,7 +1975,7 @@ pub const DeclWithHandle = struct { .array_payload => |ap| ap.identifier, .array_index => |ai| ai, .switch_payload => |sp| sp.node, - .label_decl => |ld| ld, + .label_decl => |ld| ld.label, }; } @@ -2696,7 +2699,10 @@ fn makeScopeInternal(allocator: std.mem.Allocator, context: ScopeContext, node_i }, .data = .other, }; - try scope.decls.putNoClobber(allocator, tree.tokenSlice(first_token), .{ .label_decl = first_token }); + try scope.decls.putNoClobber(allocator, tree.tokenSlice(first_token), .{ .label_decl = .{ + .label = first_token, + .block = node_idx, + } }); } try scopes.append(allocator, .{ @@ -2817,7 +2823,10 @@ fn makeScopeInternal(allocator: std.mem.Allocator, context: ScopeContext, node_i .data = .other, }; - try scope.decls.putNoClobber(allocator, tree.tokenSlice(label), .{ .label_decl = label }); + try scope.decls.putNoClobber(allocator, tree.tokenSlice(label), .{ .label_decl = .{ + .label = label, + .block = while_node.ast.then_expr, + } }); } if (while_node.payload_token) |payload| { diff --git a/src/inlay_hints.zig b/src/inlay_hints.zig index 579bbe2..4c2481c 100644 --- a/src/inlay_hints.zig +++ b/src/inlay_hints.zig @@ -243,16 +243,16 @@ fn writeCallNodeHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: /// HACK self-hosted has not implemented async yet fn callWriteNodeInlayHint(allocator: std.mem.Allocator, args: anytype) error{OutOfMemory}!void { - if (zig_builtin.zig_backend == .other or zig_builtin.zig_backend == .stage1) { - const FrameSize = @sizeOf(@Frame(writeNodeInlayHint)); - var child_frame = try allocator.alignedAlloc(u8, std.Target.stack_align, FrameSize); - defer allocator.free(child_frame); - - return await @asyncCall(child_frame, {}, writeNodeInlayHint, args); - } else { - // TODO find a non recursive solution - return @call(.{}, writeNodeInlayHint, args); - } + _ = allocator; + // if (zig_builtin.zig_backend == .other or zig_builtin.zig_backend == .stage1) { + // const FrameSize = @sizeOf(@Frame(writeNodeInlayHint)); + // var child_frame = try allocator.alignedAlloc(u8, std.Target.stack_align, FrameSize); + // defer allocator.free(child_frame); + // return await @asyncCall(child_frame, {}, writeNodeInlayHint, args); + // } else { + // TODO find a non recursive solution + return @call(.{}, writeNodeInlayHint, args); + // } } /// iterates over the ast and writes parameter hints into `builder.hints` for every function call and builtin call @@ -266,7 +266,11 @@ fn writeNodeInlayHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: const node_data = tree.nodes.items(.data); const main_tokens = tree.nodes.items(.main_token); - if (node == 0 or node > node_data.len) return; + // std.log.info("max: {d} | curr: {d}", .{ node_data.len, node }); + // if (node == 0 or node >= node_data.len) return; + if (node == 0) return; + // std.log.info("tag: {any}", .{node_tags[node]}); + // std.log.info("src: {s}", .{tree.getNodeSource(node)}); var allocator = arena.allocator(); @@ -452,7 +456,6 @@ fn writeNodeInlayHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: .bool_or, .array_access, .switch_range, - .error_value, .error_union, => { try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].lhs, range }); @@ -679,6 +682,8 @@ fn writeNodeInlayHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: try callWriteNodeInlayHint(allocator, .{ builder, arena, store, asm_node.ast.template, range }); }, + + .error_value => {}, } } diff --git a/src/references.zig b/src/references.zig index 3d1fa8a..de2bac5 100644 --- a/src/references.zig +++ b/src/references.zig @@ -20,8 +20,8 @@ pub fn labelReferences( // Find while / for / block from label -> iterate over children nodes, find break and continues, change their labels if they match. // This case can be implemented just by scanning tokens. - const first_tok = tree.firstToken(decl.decl.label_decl); - const last_tok = ast.lastToken(tree, decl.decl.label_decl); + const first_tok = tree.firstToken(decl.decl.label_decl.label); + const last_tok = ast.lastToken(tree, decl.decl.label_decl.block); var locations = std.ArrayListUnmanaged(types.Location){}; errdefer locations.deinit(allocator); From d43329a9ee7be06cb059c1219ec645f55fa2dd05 Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Wed, 14 Dec 2022 05:51:01 -0500 Subject: [PATCH 07/21] Fix labels for real this time (#819) --- src/references.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/references.zig b/src/references.zig index de2bac5..1776327 100644 --- a/src/references.zig +++ b/src/references.zig @@ -20,7 +20,7 @@ pub fn labelReferences( // Find while / for / block from label -> iterate over children nodes, find break and continues, change their labels if they match. // This case can be implemented just by scanning tokens. - const first_tok = tree.firstToken(decl.decl.label_decl.label); + const first_tok = decl.decl.label_decl.label; const last_tok = ast.lastToken(tree, decl.decl.label_decl.block); var locations = std.ArrayListUnmanaged(types.Location){}; From 3526f5fb84b89b6327fc21c6b836b842bf4db90d Mon Sep 17 00:00:00 2001 From: nullptrdevs <16590917+nullptrdevs@users.noreply.github.com> Date: Wed, 14 Dec 2022 17:58:38 -0800 Subject: [PATCH 08/21] Zig @call changes (#822) First parameter is now a `std.builtin.CallModifier`. --- build.zig | 2 +- src/data/generate-data.py | 0 src/data/master.zig | 72 ++++----------------------------------- src/inlay_hints.zig | 2 +- src/semantic_tokens.zig | 2 +- src/translate_c.zig | 2 +- 6 files changed, 11 insertions(+), 69 deletions(-) mode change 100644 => 100755 src/data/generate-data.py diff --git a/build.zig b/build.zig index 971ed16..29926d9 100644 --- a/build.zig +++ b/build.zig @@ -6,7 +6,7 @@ const zls_version = std.builtin.Version{ .major = 0, .minor = 11, .patch = 0 }; pub fn build(b: *std.build.Builder) !void { const current_zig = builtin.zig_version; - const min_zig = std.SemanticVersion.parse("0.11.0-dev.715+cffbb32d3") catch return; // Deprecated ascii symbols removed + const min_zig = std.SemanticVersion.parse("0.11.0-dev.780+6378644d4") catch return; // Changes to @call if (current_zig.order(min_zig).compare(.lt)) @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); const target = b.standardTargetOptions(.{}); diff --git a/src/data/generate-data.py b/src/data/generate-data.py old mode 100644 new mode 100755 diff --git a/src/data/master.zig b/src/data/master.zig index 9eacf7b..c5f45b4 100644 --- a/src/data/master.zig +++ b/src/data/master.zig @@ -79,24 +79,6 @@ pub const builtins = [_]Builtin{ "expression", }, }, - .{ - .name = "@asyncCall", - .signature = "@asyncCall(frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8, result_ptr, function_ptr, args: anytype) anyframe->T", - .snippet = "@asyncCall(${1:frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8}, ${2:result_ptr}, ${3:function_ptr}, ${4:args: anytype})", - .documentation = - \\`@asyncCall` performs an `async` call on a function pointer, which may or may not be an [async function](https://ziglang.org/documentation/master/#Async-Functions). - \\ - \\The provided `frame_buffer` must be large enough to fit the entire function frame. This size can be determined with [@frameSize](https://ziglang.org/documentation/master/#frameSize). To provide a too-small buffer invokes safety-checked [Undefined Behavior](https://ziglang.org/documentation/master/#Undefined-Behavior). - \\ - \\`result_ptr` is optional ([null](https://ziglang.org/documentation/master/#null) may be provided). If provided, the function call will write its result directly to the result pointer, which will be available to read after [await](https://ziglang.org/documentation/master/#Async-and-Await) completes. Any result location provided to `await` will copy the result from `result_ptr`.

{#code_begin|test|async_struct_field_fn_pointer#} {#backend_stage1#} const std = @import("std"); const expect = std.testing.expect; test "async fn pointer in a struct field" { var data: i32 = 1; const Foo = struct { bar: fn (*i32) callconv(.Async) void, }; var foo = Foo{ .bar = func }; var bytes: [64]u8 align(@alignOf(@Frame(func))) = undefined; const f = @asyncCall(&bytes, {}, foo.bar, .{&data}); try expect(data == 2); resume f; try expect(data == 4); } fn func(y: *i32) void { defer y.* += 2; y.* += 1; suspend {} }` - , - .arguments = &.{ - "frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8", - "result_ptr", - "function_ptr", - "args: anytype", - }, - }, .{ .name = "@atomicLoad", .signature = "@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: builtin.AtomicOrder) T", @@ -291,8 +273,8 @@ pub const builtins = [_]Builtin{ }, .{ .name = "@call", - .signature = "@call(options: std.builtin.CallOptions, function: anytype, args: anytype) anytype", - .snippet = "@call(${1:options: std.builtin.CallOptions}, ${2:function: anytype}, ${3:args: anytype})", + .signature = "@call(modifier: std.builtin.CallModifier, function: anytype, args: anytype) anytype", + .snippet = "@call(${1:modifier: std.builtin.CallModifier}, ${2:function: anytype}, ${3:args: anytype})", .documentation = \\Calls a function, in the same way that invoking an expression with parentheses does: \\ @@ -300,7 +282,7 @@ pub const builtins = [_]Builtin{ \\const expect = @import("std").testing.expect; \\ \\test "noinline function call" { - \\ try expect(@call(.{}, add, .{3, 9}) == 12); + \\ try expect(@call(.auto, add, .{3, 9}) == 12); \\} \\ \\fn add(a: i32, b: i32) i32 { @@ -308,10 +290,10 @@ pub const builtins = [_]Builtin{ \\} \\``` \\ - \\`@call` allows more flexibility than normal function call syntax does. The `CallOptions` struct is reproduced here:

{#syntax_block|zig|builtin.CallOptions struct#} pub const CallOptions = struct { modifier: Modifier = .auto, /// Only valid when `Modifier` is `Modifier.async_kw`. stack: ?[]align(std.Target.stack_align) u8 = null, pub const Modifier = enum { /// Equivalent to function call syntax. auto, /// Equivalent to async keyword used with function call syntax. async_kw, /// Prevents tail call optimization. This guarantees that the return /// address will point to the callsite, as opposed to the callsite's /// callsite. If the call is otherwise required to be tail-called /// or inlined, a compile error is emitted instead. never_tail, /// Guarantees that the call will not be inlined. If the call is /// otherwise required to be inlined, a compile error is emitted instead. never_inline, /// Asserts that the function call will not suspend. This allows a /// non-async function to call an async function. no_async, /// Guarantees that the call will be generated with tail call optimization. /// If this is not possible, a compile error is emitted instead. always_tail, /// Guarantees that the call will inlined at the callsite. /// If this is not possible, a compile error is emitted instead. always_inline, /// Evaluates the call at compile-time. If the call cannot be completed at /// compile-time, a compile error is emitted instead. compile_time, }; }; {#end_syntax_block#} + \\`@call` allows more flexibility than normal function call syntax does. The `CallModifier` enum is reproduced here:

{#syntax_block|zig|builtin.CallModifier struct#} pub const CallModifier = enum { /// Equivalent to function call syntax. auto, /// Equivalent to async keyword used with function call syntax. async_kw, /// Prevents tail call optimization. This guarantees that the return /// address will point to the callsite, as opposed to the callsite's /// callsite. If the call is otherwise required to be tail-called /// or inlined, a compile error is emitted instead. never_tail, /// Guarantees that the call will not be inlined. If the call is /// otherwise required to be inlined, a compile error is emitted instead. never_inline, /// Asserts that the function call will not suspend. This allows a /// non-async function to call an async function. no_async, /// Guarantees that the call will be generated with tail call optimization. /// If this is not possible, a compile error is emitted instead. always_tail, /// Guarantees that the call will inlined at the callsite. /// If this is not possible, a compile error is emitted instead. always_inline, /// Evaluates the call at compile-time. If the call cannot be completed at /// compile-time, a compile error is emitted instead. compile_time, }; {#end_syntax_block#} , .arguments = &.{ - "options: std.builtin.CallOptions", + "modifier: std.builtin.CallModifier", "function: anytype", "args: anytype", }, @@ -770,30 +752,6 @@ pub const builtins = [_]Builtin{ "float: anytype", }, }, - .{ - .name = "@frame", - .signature = "@frame() *@Frame(func)", - .snippet = "@frame()", - .documentation = - \\This function returns a pointer to the frame for a given function. This type can be [coerced](https://ziglang.org/documentation/master/#Type-Coercion) to `anyframe->T` and to `anyframe`, where `T` is the return type of the function in scope. - \\ - \\This function does not mark a suspension point, but it does cause the function in scope to become an [async function](https://ziglang.org/documentation/master/#Async-Functions). - , - .arguments = &.{}, - }, - .{ - .name = "@Frame", - .signature = "@Frame(func: anytype) type", - .snippet = "@Frame(${1:func: anytype})", - .documentation = - \\This function returns the frame type of a function. This works for [Async Functions](https://ziglang.org/documentation/master/#Async-Functions) as well as any function without a specific calling convention. - \\ - \\This type is suitable to be used as the return type of [async](https://ziglang.org/documentation/master/#Async-and-Await) which allows one to, for example, heap-allocate an async function frame:

{#code_begin|test|heap_allocated_frame#} {#backend_stage1#} const std = @import("std"); test "heap allocated frame" { const frame = try std.heap.page_allocator.create(@Frame(func)); frame.* = async func(); } fn func() void { suspend {} }` - , - .arguments = &.{ - "func: anytype", - }, - }, .{ .name = "@frameAddress", .signature = "@frameAddress() usize", @@ -807,25 +765,12 @@ pub const builtins = [_]Builtin{ , .arguments = &.{}, }, - .{ - .name = "@frameSize", - .signature = "@frameSize(func: anytype) usize", - .snippet = "@frameSize(${1:func: anytype})", - .documentation = - \\This is the same as `@sizeOf(@Frame(func))`, where `func` may be runtime-known. - \\ - \\This function is typically used in conjunction with [@asyncCall](https://ziglang.org/documentation/master/#asyncCall). - , - .arguments = &.{ - "func: anytype", - }, - }, .{ .name = "@hasDecl", .signature = "@hasDecl(comptime Container: type, comptime name: []const u8) bool", .snippet = "@hasDecl(${1:comptime Container: type}, ${2:comptime name: []const u8})", .documentation = - \\Returns whether or not a [struct](https://ziglang.org/documentation/master/#struct), [enum](https://ziglang.org/documentation/master/#enum), or [union](https://ziglang.org/documentation/master/#union) has a declaration matching `name`.

{#code_begin|test|hasDecl#} const std = @import("std"); const expect = std.testing.expect; const Foo = struct { nope: i32, pub var blah = "xxx"; const hi = 1; }; test "@hasDecl" { try expect(@hasDecl(Foo, "blah")); // Even though `hi` is private, @hasDecl returns true because this test is // in the same file scope as Foo. It would return false if Foo was declared // in a different file. try expect(@hasDecl(Foo, "hi")); // @hasDecl is for declarations; not fields. try expect(!@hasDecl(Foo, "nope")); try expect(!@hasDecl(Foo, "nope1234")); }` + \\Returns whether or not a [container](https://ziglang.org/documentation/master/#Containers) has a declaration matching `name`.

{#code_begin|test|hasDecl#} const std = @import("std"); const expect = std.testing.expect; const Foo = struct { nope: i32, pub var blah = "xxx"; const hi = 1; }; test "@hasDecl" { try expect(@hasDecl(Foo, "blah")); // Even though `hi` is private, @hasDecl returns true because this test is // in the same file scope as Foo. It would return false if Foo was declared // in a different file. try expect(@hasDecl(Foo, "hi")); // @hasDecl is for declarations; not fields. try expect(!@hasDecl(Foo, "nope")); try expect(!@hasDecl(Foo, "nope1234")); }` , .arguments = &.{ "comptime Container: type", @@ -1721,16 +1666,13 @@ pub const builtins = [_]Builtin{ \\ - [Error Union Type](https://ziglang.org/documentation/master/#Error-Union-Type) \\ - [Vectors](https://ziglang.org/documentation/master/#Vectors) \\ - [opaque](https://ziglang.org/documentation/master/#opaque) - \\ - [@Frame](https://ziglang.org/documentation/master/#Frame) \\ - `anyframe` \\ - [struct](https://ziglang.org/documentation/master/#struct) \\ - [enum](https://ziglang.org/documentation/master/#enum) \\ - [Enum Literals](https://ziglang.org/documentation/master/#Enum-Literals) \\ - [union](https://ziglang.org/documentation/master/#union) \\ - \\For these types, `@Type` is not available: - \\ - [Functions](https://ziglang.org/documentation/master/#Functions) - \\ - BoundFn + \\`@Type` is not available for [Functions](https://ziglang.org/documentation/master/#Functions). , .arguments = &.{ "comptime info: std.builtin.Type", diff --git a/src/inlay_hints.zig b/src/inlay_hints.zig index 4c2481c..94b66fe 100644 --- a/src/inlay_hints.zig +++ b/src/inlay_hints.zig @@ -251,7 +251,7 @@ fn callWriteNodeInlayHint(allocator: std.mem.Allocator, args: anytype) error{Out // return await @asyncCall(child_frame, {}, writeNodeInlayHint, args); // } else { // TODO find a non recursive solution - return @call(.{}, writeNodeInlayHint, args); + return @call(.auto, writeNodeInlayHint, args); // } } diff --git a/src/semantic_tokens.zig b/src/semantic_tokens.zig index d54464f..4257292 100644 --- a/src/semantic_tokens.zig +++ b/src/semantic_tokens.zig @@ -276,7 +276,7 @@ fn callWriteNodeTokens(allocator: std.mem.Allocator, args: anytype) WriteTokensE return await @asyncCall(child_frame, {}, writeNodeTokens, args); } else { // TODO find a non recursive solution - return @call(.{}, writeNodeTokens, args); + return @call(.auto, writeNodeTokens, args); } } diff --git a/src/translate_c.zig b/src/translate_c.zig index dcc7a31..d58dca3 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -52,7 +52,7 @@ fn callConvertCIncludeInternal(allocator: std.mem.Allocator, args: anytype) erro return await @asyncCall(child_frame, {}, convertCIncludeInternal, args); } else { // TODO find a non recursive solution - return @call(.{}, convertCIncludeInternal, args); + return @call(.auto, convertCIncludeInternal, args); } } From c39a4eb2ab2bc99df61a849ef7a0bb5719fee08e Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Thu, 15 Dec 2022 19:03:09 +0100 Subject: [PATCH 09/21] simplify & refactor analysis code (#823) --- src/Server.zig | 51 +++++--------------- src/analysis.zig | 119 +++++++++++++++++++---------------------------- 2 files changed, 60 insertions(+), 110 deletions(-) diff --git a/src/Server.zig b/src/Server.zig index 61d2a84..7c38d92 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -872,11 +872,7 @@ fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle) error{OutO const end = offsets.tokenToLoc(tree, last_token).end; break :def tree.source[start..end]; }, - .pointer_payload => |payload| tree.tokenSlice(payload.name), - .array_payload => |payload| handle.tree.tokenSlice(payload.identifier), - .array_index => |payload| handle.tree.tokenSlice(payload), - .switch_payload => |payload| tree.tokenSlice(payload.node), - .label_decl => |label_decl| tree.tokenSlice(label_decl.label), + .pointer_payload, .array_payload, .array_index, .switch_payload, .label_decl => tree.tokenSlice(decl_handle.nameToken()), }; var bound_type_params = analysis.BoundTypeParams{}; @@ -1160,43 +1156,18 @@ fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.Decl .insertTextFormat = .PlainText, }); }, - .pointer_payload => |payload| { + .pointer_payload, + .array_payload, + .array_index, + .switch_payload, + .label_decl, + => { + const name = tree.tokenSlice(decl_handle.nameToken()); + try context.completions.append(allocator, .{ - .label = tree.tokenSlice(payload.name), + .label = name, .kind = .Variable, - .insertText = tree.tokenSlice(payload.name), - .insertTextFormat = .PlainText, - }); - }, - .array_payload => |payload| { - try context.completions.append(allocator, .{ - .label = tree.tokenSlice(payload.identifier), - .kind = .Variable, - .insertText = tree.tokenSlice(payload.identifier), - .insertTextFormat = .PlainText, - }); - }, - .array_index => |payload| { - try context.completions.append(allocator, .{ - .label = tree.tokenSlice(payload), - .kind = .Variable, - .insertText = tree.tokenSlice(payload), - .insertTextFormat = .PlainText, - }); - }, - .switch_payload => |payload| { - try context.completions.append(allocator, .{ - .label = tree.tokenSlice(payload.node), - .kind = .Variable, - .insertText = tree.tokenSlice(payload.node), - .insertTextFormat = .PlainText, - }); - }, - .label_decl => |label_decl| { - try context.completions.append(allocator, .{ - .label = tree.tokenSlice(label_decl.label), - .kind = .Variable, - .insertText = tree.tokenSlice(label_decl.label), + .insertText = name, .insertTextFormat = .PlainText, }); }, diff --git a/src/analysis.zig b/src/analysis.zig index 65d287a..a559d97 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -275,10 +275,11 @@ pub fn getDeclNameToken(tree: Ast, node: Ast.Node.Index) ?Ast.TokenIndex { const main_token = tree.nodes.items(.main_token)[node]; return switch (tags[node]) { // regular declaration names. + 1 to mut token because name comes after 'const'/'var' - .local_var_decl => tree.localVarDecl(node).ast.mut_token + 1, - .global_var_decl => tree.globalVarDecl(node).ast.mut_token + 1, - .simple_var_decl => tree.simpleVarDecl(node).ast.mut_token + 1, - .aligned_var_decl => tree.alignedVarDecl(node).ast.mut_token + 1, + .local_var_decl, + .global_var_decl, + .simple_var_decl, + .aligned_var_decl, + => ast.varDecl(tree, node).?.ast.mut_token + 1, // function declaration names .fn_proto, .fn_proto_multi, @@ -291,20 +292,9 @@ pub fn getDeclNameToken(tree: Ast, node: Ast.Node.Index) ?Ast.TokenIndex { }, // containers - .container_field => blk: { - const field = tree.containerField(node); - if (field.ast.tuple_like) break :blk null; - break :blk field.ast.main_token; - }, - .container_field_init => blk: { - const field = tree.containerFieldInit(node); - if (field.ast.tuple_like) break :blk null; - break :blk field.ast.main_token; - }, - .container_field_align => blk: { - const field = tree.containerFieldAlign(node); - if (field.ast.tuple_like) break :blk null; - break :blk field.ast.main_token; + .container_field, .container_field_init, .container_field_align => { + const field = ast.containerField(tree, node).?.ast; + return if (field.tuple_like) null else field.main_token; }, .identifier => main_token, @@ -1410,45 +1400,39 @@ pub fn nodeToString(tree: Ast, node: Ast.Node.Index) ?[]const u8 { const data = tree.nodes.items(.data); const main_token = tree.nodes.items(.main_token)[node]; var buf: [1]Ast.Node.Index = undefined; - switch (tree.nodes.items(.tag)[node]) { - .container_field => { - const field = tree.containerField(node).ast; + return switch (tree.nodes.items(.tag)[node]) { + .container_field, + .container_field_init, + .container_field_align, + => { + const field = ast.containerField(tree, node).?.ast; return if (field.tuple_like) null else tree.tokenSlice(field.main_token); }, - .container_field_init => { - const field = tree.containerFieldInit(node).ast; - return if (field.tuple_like) null else tree.tokenSlice(field.main_token); - }, - .container_field_align => { - const field = tree.containerFieldAlign(node).ast; - return if (field.tuple_like) null else tree.tokenSlice(field.main_token); - }, - .error_value => return tree.tokenSlice(data[node].rhs), - .identifier => return tree.tokenSlice(main_token), + .error_value => tree.tokenSlice(data[node].rhs), + .identifier => tree.tokenSlice(main_token), .fn_proto, .fn_proto_multi, .fn_proto_one, .fn_proto_simple, .fn_decl, - => if (ast.fnProto(tree, node, &buf).?.name_token) |name| - return tree.tokenSlice(name), - .field_access => return ast.tokenSlice(tree, data[node].rhs) catch return null, + => if (ast.fnProto(tree, node, &buf).?.name_token) |name| tree.tokenSlice(name) else null, + .field_access => tree.tokenSlice(data[node].rhs), .call, .call_comma, .async_call, .async_call_comma, - => return tree.tokenSlice(tree.callFull(node).ast.lparen - 1), + => tree.tokenSlice(tree.callFull(node).ast.lparen - 1), .call_one, .call_one_comma, .async_call_one, .async_call_one_comma, - => return tree.tokenSlice(tree.callOne(&buf, node).ast.lparen - 1), - .test_decl => if (data[node].lhs != 0) - return tree.tokenSlice(data[node].lhs), - else => |tag| log.debug("INVALID: {}", .{tag}), - } - - return null; + => tree.tokenSlice(tree.callOne(&buf, node).ast.lparen - 1), + .test_decl => if (data[node].lhs != 0) tree.tokenSlice(data[node].lhs) else null, + else => |tag| { + log.debug("INVALID: {}", .{tag}); + return null; + }, + }; } fn nodeContainsSourceIndex(tree: Ast, node: Ast.Node.Index, source_index: usize) bool { @@ -1669,38 +1653,33 @@ pub fn getPositionContext(allocator: std.mem.Allocator, text: []const u8, doc_in } } - return block: { - if (stack.popOrNull()) |state| { - switch (state.ctx) { - .empty => {}, - .label => |filled| { - // We need to check this because the state could be a filled - // label if only a space follows it - if (!filled or line[line.len - 1] != ' ') { - break :block state.ctx; - } - }, - else => break :block state.ctx, - } + if (stack.popOrNull()) |state| { + switch (state.ctx) { + .empty => {}, + .label => |filled| { + // We need to check this because the state could be a filled + // label if only a space follows it + if (!filled or line[line.len - 1] != ' ') { + return state.ctx; + } + }, + else => return state.ctx, } + } - if (line.len == 0) return .empty; + if (line.len == 0) return .empty; - var held_line = try allocator.dupeZ(u8, offsets.locToSlice(text, line_loc)); - defer allocator.free(held_line); + var held_line = try allocator.dupeZ(u8, offsets.locToSlice(text, line_loc)); + defer allocator.free(held_line); - switch (line[0]) { - 'a'...'z', 'A'...'Z', '_', '@' => {}, - else => break :block .empty, - } - var tokenizer = std.zig.Tokenizer.init(held_line); - const tok = tokenizer.next(); - if (tok.tag == .identifier) { - break :block PositionContext{ .var_access = tok.loc }; - } else { - break :block .empty; - } - }; + switch (line[0]) { + 'a'...'z', 'A'...'Z', '_', '@' => {}, + else => return .empty, + } + var tokenizer = std.zig.Tokenizer.init(held_line); + const tok = tokenizer.next(); + + return if (tok.tag == .identifier) PositionContext{ .var_access = tok.loc } else .empty; } fn addOutlineNodes(allocator: std.mem.Allocator, tree: Ast, child: Ast.Node.Index, context: *GetDocumentSymbolsContext) anyerror!void { From 68ac6f9f30adcd44c0e22219ffcef5fbfd9606d8 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Thu, 15 Dec 2022 23:01:42 +0100 Subject: [PATCH 10/21] remove `ast.tokenSlice()` (#824) --- src/ComptimeInterpreter.zig | 157 ++++++++++++++++++------------------ src/analysis.zig | 4 +- src/ast.zig | 23 ------ src/code_actions.zig | 32 ++++---- src/references.zig | 3 +- src/semantic_tokens.zig | 3 +- 6 files changed, 97 insertions(+), 125 deletions(-) diff --git a/src/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 231f60d..55104c0 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -122,17 +122,17 @@ pub const TypeInfo = union(enum) { /// Hack to get anytype working; only valid on fnparams @"anytype", - @"type", - @"bool", + type, + bool, @"struct": Struct, pointer: Pointer, @"fn": Fn, int: Int, - @"comptime_int", + comptime_int, float: u16, - @"comptime_float", + comptime_float, array: Array, @@ -222,7 +222,7 @@ pub const Value = struct { interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, - @"type": Type, + type: Type, value_data: *ValueData, pub fn eql(value: Value, other_value: Value) bool { @@ -233,8 +233,8 @@ pub const Value = struct { pub const ValueData = union(enum) { // TODO: Support larger ints, floats; bigints? - @"type": Type, - @"bool": bool, + type: Type, + bool: bool, @"struct": struct {}, /// This is what a pointer is; we don't need to map @@ -261,7 +261,7 @@ pub const ValueData = union(enum) { // std.enums. // std.meta.activeTag(u: anytype) switch (data.*) { - .@"bool" => return data.@"bool" == other_data.@"bool", + .bool => return data.bool == other_data.bool, .big_int => return data.big_int.eq(other_data.big_int), .unsigned_int => return data.unsigned_int == other_data.unsigned_int, .signed_int => return data.signed_int == other_data.signed_int, @@ -287,7 +287,7 @@ pub const FieldDefinition = struct { /// Store name so tree doesn't need to be used to access field name /// When the field is a tuple field, `name` will be an empty slice name: []const u8, - @"type": Type, + type: Type, default_value: ?Value, }; @@ -333,15 +333,15 @@ pub const Declaration = struct { if (var_decl.ast.type_node != 0) { var type_val = try (try interpreter.interpret(var_decl.ast.type_node, decl.scope, .{})).getValue(); - if (type_val.@"type".getTypeInfo() != .@"type") { + if (type_val.type.getTypeInfo() != .type) { try interpreter.recordError( decl.node_idx, "expected_type", - std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(type_val.@"type".getTypeInfo())}) catch return error.CriticalAstFailure, + std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(type_val.type.getTypeInfo())}) catch return error.CriticalAstFailure, ); return error.InvalidCast; } - value = try interpreter.cast(var_decl.ast.type_node, type_val.value_data.@"type", value); + value = try interpreter.cast(var_decl.ast.type_node, type_val.value_data.type, value); } decl.value = value; @@ -403,15 +403,15 @@ pub const TypeInfoFormatter = struct { .unsigned => try writer.print("u{d}", .{ii.bits}), }, // TODO .float => |f| try writer.print("f{d}", .{f}), - .@"comptime_int" => try writer.writeAll("comptime_int"), - .@"comptime_float" => try writer.writeAll("comptime_float"), - .@"type" => try writer.writeAll("type"), - .@"bool" => try writer.writeAll("bool"), + .comptime_int => try writer.writeAll("comptime_int"), + .comptime_float => try writer.writeAll("comptime_float"), + .type => try writer.writeAll("type"), + .bool => try writer.writeAll("bool"), .@"struct" => |s| { try writer.writeAll("struct {"); var field_iterator = s.fields.iterator(); while (field_iterator.next()) |di| { - try writer.print("{s}: {s}, ", .{ di.key_ptr.*, value.interpreter.formatTypeInfo(di.value_ptr.*.@"type".getTypeInfo()) }); + try writer.print("{s}: {s}, ", .{ di.key_ptr.*, value.interpreter.formatTypeInfo(di.value_ptr.*.type.getTypeInfo()) }); } var iterator = s.scope.declarations.iterator(); @@ -421,7 +421,7 @@ pub const TypeInfoFormatter = struct { if (decl.value) |sv| { try writer.print("const {s}: {any} = { }, ", .{ decl.name, - value.interpreter.formatTypeInfo(sv.@"type".getTypeInfo()), + value.interpreter.formatTypeInfo(sv.type.getTypeInfo()), value.interpreter.formatValue(sv), }); } else { @@ -431,7 +431,7 @@ pub const TypeInfoFormatter = struct { if (decl.value) |sv| { try writer.print("var {s}: {any} = { }, ", .{ decl.name, - value.interpreter.formatTypeInfo(sv.@"type".getTypeInfo()), + value.interpreter.formatTypeInfo(sv.type.getTypeInfo()), value.interpreter.formatValue(sv), }); } else { @@ -459,17 +459,17 @@ pub const ValueFormatter = struct { _ = options; var value = form.val; - var ti = value.@"type".getTypeInfo(); + var ti = value.type.getTypeInfo(); return switch (ti) { - .int, .@"comptime_int" => switch (value.value_data.*) { + .int, .comptime_int => switch (value.value_data.*) { .unsigned_int => |a| try writer.print("{d}", .{a}), .signed_int => |a| try writer.print("{d}", .{a}), .big_int => |a| try writer.print("{d}", .{a}), else => unreachable, }, - .@"type" => try writer.print("{ }", .{form.interpreter.formatTypeInfo(value.value_data.@"type".getTypeInfo())}), + .type => try writer.print("{ }", .{form.interpreter.formatTypeInfo(value.value_data.type.getTypeInfo())}), else => try writer.print("UnimplementedValuePrint", .{}), }; } @@ -672,14 +672,14 @@ pub fn cast( const value_data = value.value_data; const to_type_info = dest_type.getTypeInfo(); - const from_type_info = value.@"type".getTypeInfo(); + const from_type_info = value.type.getTypeInfo(); // TODO: Implement more implicit casts if (from_type_info.eql(to_type_info)) return value; const err = switch (from_type_info) { - .@"comptime_int" => switch (to_type_info) { + .comptime_int => switch (to_type_info) { .int => { if (value_data.bitCount().? > to_type_info.int.bits) { switch (value_data.*) { @@ -706,7 +706,7 @@ pub fn cast( .interpreter = interpreter, .node_idx = node_idx, - .@"type" = dest_type, + .type = dest_type, .value_data = value.value_data, }; } @@ -782,22 +782,23 @@ pub fn interpret( else try (try interpreter.interpret(field_info.ast.value_expr, container_scope, .{})).getValue(); - if (init_type_value.@"type".getTypeInfo() != .@"type") { + if (init_type_value.type.getTypeInfo() != .type) { try interpreter.recordError( field_info.ast.type_expr, "expected_type", - try std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(init_type_value.@"type".getTypeInfo())}), + try std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(init_type_value.type.getTypeInfo())}), ); continue; } const name = if (field_info.ast.tuple_like) &[0]u8{} - else tree.tokenSlice(field_info.ast.main_token); + else + tree.tokenSlice(field_info.ast.main_token); const field = FieldDefinition{ .node_idx = member, .name = name, - .@"type" = init_type_value.value_data.@"type", + .type = init_type_value.value_data.type, .default_value = default_value, // TODO: Default values // .@"type" = T: { @@ -817,8 +818,8 @@ pub fn interpret( return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = try interpreter.createValueData(.{ .@"type" = cont_type }), + .type = try interpreter.createType(node_idx, .{ .type = {} }), + .value_data = try interpreter.createValueData(.{ .type = cont_type }), } }; }, .global_var_decl, @@ -906,29 +907,29 @@ pub fn interpret( if (std.mem.eql(u8, "bool", value)) return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }) }), + .type = try interpreter.createType(node_idx, .{ .type = {} }), + .value_data = try interpreter.createValueData(.{ .type = try interpreter.createType(node_idx, .{ .bool = {} }) }), } }; if (std.mem.eql(u8, "true", value)) return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), - .value_data = try interpreter.createValueData(.{ .@"bool" = true }), + .type = try interpreter.createType(node_idx, .{ .bool = {} }), + .value_data = try interpreter.createValueData(.{ .bool = true }), } }; if (std.mem.eql(u8, "false", value)) return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), - .value_data = try interpreter.createValueData(.{ .@"bool" = false }), + .type = try interpreter.createType(node_idx, .{ .bool = {} }), + .value_data = try interpreter.createValueData(.{ .bool = false }), } }; if (value.len == 5 and (value[0] == 'u' or value[0] == 'i') and std.mem.eql(u8, "size", value[1..])) return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), + .type = try interpreter.createType(node_idx, .{ .type = {} }), .value_data = try interpreter.createValueData(.{ - .@"type" = try interpreter.createType(node_idx, .{ + .type = try interpreter.createType(node_idx, .{ .int = .{ .signedness = if (value[0] == 'u') .unsigned else .signed, .bits = 64, // TODO: Platform specific @@ -942,15 +943,15 @@ pub fn interpret( return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }) }), + .type = try interpreter.createType(node_idx, .{ .type = {} }), + .value_data = try interpreter.createValueData(.{ .type = try interpreter.createType(node_idx, .{ .type = {} }) }), } }; } else if (value.len >= 2 and (value[0] == 'u' or value[0] == 'i')) int: { return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ + .type = try interpreter.createType(node_idx, .{ .type = {} }), + .value_data = try interpreter.createValueData(.{ .type = try interpreter.createType(node_idx, .{ .int = .{ .signedness = if (value[0] == 'u') .unsigned else .signed, .bits = std.fmt.parseInt(u16, value[1..], 10) catch break :int, @@ -973,12 +974,12 @@ pub fn interpret( }, .field_access => { if (data[node_idx].rhs == 0) return error.CriticalAstFailure; - const rhs_str = ast.tokenSlice(tree, data[node_idx].rhs) catch return error.CriticalAstFailure; + const rhs_str = tree.tokenSlice(data[node_idx].rhs); var ir = try interpreter.interpret(data[node_idx].lhs, scope, options); var irv = try ir.getValue(); - var sub_scope = irv.value_data.@"type".getTypeInfo().getScopeOfType() orelse return error.IdentifierNotFound; + var sub_scope = irv.value_data.type.getTypeInfo().getScopeOfType() orelse return error.IdentifierNotFound; var scope_sub_decl = sub_scope.interpreter.huntItDown(sub_scope, rhs_str, options) catch |err| { if (err == error.IdentifierNotFound) try interpreter.recordError( node_idx, @@ -1013,7 +1014,7 @@ pub fn interpret( // TODO: Don't evaluate runtime ifs // if (options.observe_values) { const ir = try interpreter.interpret(iff.ast.cond_expr, scope, options); - if ((try ir.getValue()).value_data.@"bool") { + if ((try ir.getValue()).value_data.bool) { return try interpreter.interpret(iff.ast.then_expr, scope, options); } else { if (iff.ast.else_expr != 0) { @@ -1027,8 +1028,8 @@ pub fn interpret( return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), - .value_data = try interpreter.createValueData(.{ .@"bool" = (try a.getValue()).eql(try b.getValue()) }), + .type = try interpreter.createType(node_idx, .{ .bool = {} }), + .value_data = try interpreter.createValueData(.{ .bool = (try a.getValue()).eql(try b.getValue()) }), } }; // a.getValue().eql(b.getValue()) }, @@ -1040,7 +1041,7 @@ pub fn interpret( .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"comptime_int" = {} }), + .type = try interpreter.createType(node_idx, .{ .comptime_int = {} }), .value_data = try interpreter.createValueData(switch (nl) { .float => .{ .float = try std.fmt.parseFloat(f64, s) }, .int => if (s[0] == '-') ValueData{ .signed_int = try std.fmt.parseInt(i64, s, 0) } else ValueData{ .unsigned_int = try std.fmt.parseInt(u64, s, 0) }, @@ -1080,7 +1081,7 @@ pub fn interpret( var to_value = try ir.getValue(); var from_value = (try (try interpreter.interpret(data[node_idx].rhs, scope.?, options)).getValue()); - to_value.value_data.* = (try interpreter.cast(node_idx, to_value.@"type", from_value)).value_data.*; + to_value.value_data.* = (try interpreter.cast(node_idx, to_value.type, from_value)).value_data.*; return InterpretResult{ .nothing = {} }; }, @@ -1118,7 +1119,7 @@ pub fn interpret( try writer.writeAll("indeterminate"); continue; }; - try writer.print("@as({s}, {s})", .{ interpreter.formatTypeInfo(value.@"type".getTypeInfo()), interpreter.formatValue(value) }); + try writer.print("@as({s}, {s})", .{ interpreter.formatTypeInfo(value.type.getTypeInfo()), interpreter.formatValue(value) }); if (index != params.len - 1) try writer.writeAll(", "); } @@ -1147,7 +1148,7 @@ pub fn interpret( return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"struct" = .{ .scope = try interpreter.newScope(null, 0) } }), + .type = try interpreter.createType(node_idx, .{ .@"struct" = .{ .scope = try interpreter.newScope(null, 0) } }), .value_data = try interpreter.createValueData(.{ .@"struct" = .{} }), } }; } @@ -1161,8 +1162,8 @@ pub fn interpret( return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = try interpreter.createValueData(.{ .@"type" = handle.interpreter.?.root_type.? }), + .type = try interpreter.createType(node_idx, .{ .type = {} }), + .value_data = try interpreter.createValueData(.{ .type = handle.interpreter.?.root_type.? }), } }; } @@ -1173,8 +1174,8 @@ pub fn interpret( return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }), - .value_data = try interpreter.createValueData(.{ .@"type" = value.@"type" }), + .type = try interpreter.createType(node_idx, .{ .type = {} }), + .value_data = try interpreter.createValueData(.{ .type = value.type }), } }; } @@ -1184,17 +1185,17 @@ pub fn interpret( const value = try (try interpreter.interpret(params[0], scope, options)).getValue(); const field_name = try (try interpreter.interpret(params[1], scope, options)).getValue(); - if (value.@"type".getTypeInfo() != .@"type") return error.InvalidBuiltin; - if (field_name.@"type".getTypeInfo() != .@"pointer") return error.InvalidBuiltin; // Check if it's a []const u8 + if (value.type.getTypeInfo() != .type) return error.InvalidBuiltin; + if (field_name.type.getTypeInfo() != .pointer) return error.InvalidBuiltin; // Check if it's a []const u8 - const ti = value.value_data.@"type".getTypeInfo(); + const ti = value.value_data.type.getTypeInfo(); if (ti.getScopeOfType() == null) return error.InvalidBuiltin; return InterpretResult{ .value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }), - .value_data = try interpreter.createValueData(.{ .@"bool" = ti.getScopeOfType().?.declarations.contains(field_name.value_data.slice_of_const_u8) }), + .type = try interpreter.createType(node_idx, .{ .bool = {} }), + .value_data = try interpreter.createValueData(.{ .bool = ti.getScopeOfType().?.declarations.contains(field_name.value_data.slice_of_const_u8) }), } }; } @@ -1204,9 +1205,9 @@ pub fn interpret( const as_type = try (try interpreter.interpret(params[0], scope, options)).getValue(); const value = try (try interpreter.interpret(params[1], scope, options)).getValue(); - if (as_type.@"type".getTypeInfo() != .@"type") return error.InvalidBuiltin; + if (as_type.type.getTypeInfo() != .type) return error.InvalidBuiltin; - return InterpretResult{ .value = try interpreter.cast(node_idx, as_type.value_data.@"type", value) }; + return InterpretResult{ .value = try interpreter.cast(node_idx, as_type.value_data.type, value) }; } log.err("Builtin not implemented: {s}", .{call_name}); @@ -1220,7 +1221,7 @@ pub fn interpret( // TODO: This is literally the wrong type lmao // the actual type is *[len:0]u8 because we're pointing // to a fixed size value in the data(?) section (when we're compilign zig code) - .@"type" = try interpreter.createType(node_idx, .{ + .type = try interpreter.createType(node_idx, .{ .pointer = .{ .size = .one, .is_const = true, @@ -1290,7 +1291,7 @@ pub fn interpret( var value = Value{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, type_info), + .type = try interpreter.createType(node_idx, type_info), .value_data = try interpreter.createValueData(.{ .@"fn" = {} }), }; @@ -1299,7 +1300,7 @@ pub fn interpret( .scope = scope.?, .node_idx = node_idx, .name = name, - .@"value" = value, + .value = value, }); return InterpretResult{ .nothing = {} }; @@ -1339,13 +1340,13 @@ pub fn interpret( .bool_not => { const result = try interpreter.interpret(data[node_idx].lhs, scope, .{}); const value = (try result.getValue()); - if (value.value_data.* != .@"bool") return error.InvalidOperation; + if (value.value_data.* != .bool) return error.InvalidOperation; return InterpretResult{ .value = .{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = value.@"type", - .value_data = try interpreter.createValueData(.{ .@"bool" = !value.value_data.@"bool" }), + .type = value.type, + .value_data = try interpreter.createValueData(.{ .bool = !value.value_data.bool }), }, }; }, @@ -1360,18 +1361,18 @@ pub fn interpret( .value = .{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = try interpreter.createType(node_idx, .{ + .type = try interpreter.createType(node_idx, .{ .pointer = .{ .size = .one, .is_const = false, .is_volatile = false, - .child = value.@"type", + .child = value.type, .is_allowzero = false, .sentinel = null, }, }), - .value_data = try interpreter.createValueData(.{ .@"one_ptr" = value.value_data }), + .value_data = try interpreter.createValueData(.{ .one_ptr = value.value_data }), }, }; }, @@ -1379,7 +1380,7 @@ pub fn interpret( const result = try interpreter.interpret(data[node_idx].lhs, scope, .{}); const value = (try result.getValue()); - const ti = value.@"type".getTypeInfo(); + const ti = value.type.getTypeInfo(); if (ti != .pointer) { try interpreter.recordError(node_idx, "invalid_deref", try std.fmt.allocPrint(interpreter.allocator, "cannot deference non-pointer", .{})); @@ -1392,7 +1393,7 @@ pub fn interpret( .value = .{ .interpreter = interpreter, .node_idx = node_idx, - .@"type" = ti.pointer.child, + .type = ti.pointer.child, .value_data = value.value_data.one_ptr, }, }; @@ -1439,11 +1440,11 @@ pub fn call( while (ast.nextFnParam(&arg_it)) |param| { if (arg_index >= arguments.len) return error.MissingArguments; var tex = try (try interpreter.interpret(param.type_expr, fn_scope, options)).getValue(); - if (tex.@"type".getTypeInfo() != .@"type") { + if (tex.type.getTypeInfo() != .type) { try interpreter.recordError( param.type_expr, "expected_type", - std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(tex.@"type".getTypeInfo())}) catch return error.CriticalAstFailure, + std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(tex.type.getTypeInfo())}) catch return error.CriticalAstFailure, ); return error.InvalidCast; } @@ -1452,7 +1453,7 @@ pub fn call( .scope = fn_scope, .node_idx = param.type_expr, .name = tree.tokenSlice(nt), - .value = try interpreter.cast(arguments[arg_index].node_idx, tex.value_data.@"type", arguments[arg_index]), + .value = try interpreter.cast(arguments[arg_index].node_idx, tex.value_data.type, arguments[arg_index]), }; try fn_scope.declarations.put(interpreter.allocator, tree.tokenSlice(nt), decl); arg_index += 1; @@ -1467,7 +1468,7 @@ pub fn call( .scope = fn_scope, .result = switch (result) { .@"return", .nothing => .{ .nothing = {} }, // nothing could be due to an error - .@"return_with_value" => |v| .{ .value = v }, + .return_with_value => |v| .{ .value = v }, else => @panic("bruh"), }, }; diff --git a/src/analysis.zig b/src/analysis.zig index a559d97..09a6ad3 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -864,7 +864,6 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl }, .field_access => { if (datas[node].rhs == 0) return null; - const rhs_str = ast.tokenSlice(tree, datas[node].rhs) catch return null; // If we are accessing a pointer type, remove one pointerness level :) const left_type = try resolveFieldAccessLhsType( @@ -881,11 +880,12 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl .other => |n| n, else => return null, }; + if (try lookupSymbolContainer( store, arena, .{ .node = left_type_node, .handle = left_type.handle }, - rhs_str, + tree.tokenSlice(datas[node].rhs), !left_type.type.is_type_val, )) |child| { return try child.resolveType(store, arena, bound_type_params); diff --git a/src/ast.zig b/src/ast.zig index 5056a9c..af7cb00 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -1227,26 +1227,3 @@ pub fn nextFnParam(it: *Ast.full.FnProto.Iterator) ?Ast.full.FnProto.Param { it.tok_flag = false; } } - -/// A modified version of tree.tokenSlice that returns an error.UnexpectedToken if the tokenizer encounters an unexpected token -// https://github.com/zigtools/zls/issues/381 -pub fn tokenSlice(tree: Ast, token_index: Ast.TokenIndex) ![]const u8 { - const token_starts = tree.tokens.items(.start); - const token_tags = tree.tokens.items(.tag); - const token_tag = token_tags[token_index]; - - // Many tokens can be determined entirely by their tag. - if (token_tag.lexeme()) |lexeme| { - return lexeme; - } - - // For some tokens, re-tokenization is needed to find the end. - var tokenizer: std.zig.Tokenizer = .{ - .buffer = tree.source, - .index = token_starts[token_index], - .pending_invalid_token = null, - }; - const token = tokenizer.next(); - if (token.tag != token_tag) return error.UnexpectedToken; // assert(token.tag == token_tag); - return tree.source[token.loc.start..token.loc.end]; -} diff --git a/src/code_actions.zig b/src/code_actions.zig index bb7160a..e17fa57 100644 --- a/src/code_actions.zig +++ b/src/code_actions.zig @@ -15,11 +15,7 @@ pub const Builder = struct { handle: *const DocumentStore.Handle, offset_encoding: offsets.Encoding, - pub fn generateCodeAction( - builder: *Builder, - diagnostic: types.Diagnostic, - actions: *std.ArrayListUnmanaged(types.CodeAction) - ) error{OutOfMemory}!void { + pub fn generateCodeAction(builder: *Builder, diagnostic: types.Diagnostic, actions: *std.ArrayListUnmanaged(types.CodeAction)) error{OutOfMemory}!void { const kind = DiagnosticKind.parse(diagnostic.message) orelse return; const loc = offsets.rangeToLoc(builder.handle.text, diagnostic.range, builder.offset_encoding); @@ -30,7 +26,7 @@ pub const Builder = struct { .@"local constant" => try handleUnusedVariableOrConstant(builder, actions, loc), .@"local variable" => try handleUnusedVariableOrConstant(builder, actions, loc), .@"loop index capture" => try handleUnusedIndexCapture(builder, actions, loc), - .@"capture" => try handleUnusedCapture(builder, actions, loc), + .capture => try handleUnusedCapture(builder, actions, loc), }, .non_camelcase_fn => try handleNonCamelcaseFunction(builder, actions, loc), .pointless_discard => try handlePointlessDiscard(builder, actions, loc), @@ -244,21 +240,21 @@ fn detectIndentation(source: []const u8) []const u8 { // Essentially I'm looking for the first indentation in the file. var i: usize = 0; var len = source.len - 1; // I need 1 look-ahead - while(i < len) : (i += 1) { - if(source[i] != '\n') continue; + while (i < len) : (i += 1) { + if (source[i] != '\n') continue; i += 1; - if(source[i] == '\t') return "\t"; + if (source[i] == '\t') return "\t"; var space_count: usize = 0; - while(i < source.len and source[i] == ' ') : (i += 1) { + while (i < source.len and source[i] == ' ') : (i += 1) { space_count += 1; } - if(source[i] == '\n') { // Some editors mess up indentation of empty lines + if (source[i] == '\n') { // Some editors mess up indentation of empty lines i -= 1; continue; } - if(space_count == 0) continue; - if(source[i] == '/') continue; // Comments sometimes have additional alignment. - if(source[i] == '\\') continue; // multi-line strings might as well. + if (space_count == 0) continue; + if (source[i] == '/') continue; // Comments sometimes have additional alignment. + if (source[i] == '\\') continue; // multi-line strings might as well. return source[i - space_count .. i]; } return " " ** 4; // recommended style @@ -298,14 +294,14 @@ fn createCamelcaseText(allocator: std.mem.Allocator, identifier: []const u8) ![] fn createDiscardText(builder: *Builder, identifier_name: []const u8, declaration_start: usize, add_block_indentation: bool) ![]const u8 { const indent = find_indent: { const line = offsets.lineSliceUntilIndex(builder.handle.text, declaration_start); - for(line) |char, i| { - if(!std.ascii.isWhitespace(char)) { + for (line) |char, i| { + if (!std.ascii.isWhitespace(char)) { break :find_indent line[0..i]; } } break :find_indent line; }; - const additional_indent = if(add_block_indentation) detectIndentation(builder.handle.text) else ""; + const additional_indent = if (add_block_indentation) detectIndentation(builder.handle.text) else ""; const allocator = builder.arena.allocator(); const new_text_len = 1 + indent.len + additional_indent.len + "_ = ;".len + identifier_name.len; @@ -374,7 +370,7 @@ const DiagnosticKind = union(enum) { @"local constant", @"local variable", @"loop index capture", - @"capture", + capture, }; const DiscardCat = enum { diff --git a/src/references.zig b/src/references.zig index 1776327..3da4e88 100644 --- a/src/references.zig +++ b/src/references.zig @@ -337,7 +337,6 @@ fn symbolReferencesInternal( .field_access => { try symbolReferencesInternal(builder, datas[node].lhs, handle, false); - const rhs_str = ast.tokenSlice(tree, datas[node].rhs) catch return; var bound_type_params = analysis.BoundTypeParams{}; const left_type = try analysis.resolveFieldAccessLhsType( builder.store, @@ -358,7 +357,7 @@ fn symbolReferencesInternal( builder.store, builder.arena, .{ .node = left_type_node, .handle = left_type.handle }, - rhs_str, + tree.tokenSlice(datas[node].rhs), !left_type.type.is_type_val, )) orelse return; diff --git a/src/semantic_tokens.zig b/src/semantic_tokens.zig index 4257292..48c8f20 100644 --- a/src/semantic_tokens.zig +++ b/src/semantic_tokens.zig @@ -862,7 +862,6 @@ fn writeNodeTokens(builder: *Builder, maybe_node: ?Ast.Node.Index) WriteTokensEr .field_access => { const data = node_data[node]; if (data.rhs == 0) return; - const rhs_str = ast.tokenSlice(tree, data.rhs) catch return; try callWriteNodeTokens(allocator, .{ builder, data.lhs }); @@ -889,7 +888,7 @@ fn writeNodeTokens(builder: *Builder, maybe_node: ?Ast.Node.Index) WriteTokensEr builder.store, builder.arena, .{ .node = left_type_node, .handle = lhs_type.handle }, - rhs_str, + tree.tokenSlice(data.rhs), !lhs_type.type.is_type_val, )) |decl_type| { switch (decl_type.decl.*) { From e1973afafc4682042f3d4bec972126734a90cfb6 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Fri, 16 Dec 2022 21:24:03 +0100 Subject: [PATCH 11/21] fix cimport duplicate messages & crash (#828) --- src/DocumentStore.zig | 6 +++++- src/Server.zig | 20 -------------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index a475af6..13dc1cb 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -317,7 +317,11 @@ fn garbageCollectionCImports(self: *DocumentStore) error{OutOfMemory}!void { continue; } var kv = self.cimports.fetchSwapRemove(hash).?; - std.log.debug("Destroying cimport {s}", .{kv.value.success}); + const message = switch (kv.value) { + .failure => "", + .success => |uri| uri, + }; + std.log.debug("Destroying cimport {s}", .{message}); kv.value.deinit(self.allocator); } } diff --git a/src/Server.zig b/src/Server.zig index 7c38d92..0a7bc8c 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -180,26 +180,6 @@ fn publishDiagnostics(server: *Server, writer: anytype, handle: DocumentStore.Ha }); } - for (handle.cimports.items(.hash)) |hash, i| { - 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 - - const node = handle.cimports.items(.node)[i]; - try diagnostics.append(allocator, .{ - .range = offsets.nodeToRange(handle.tree, node, server.offset_encoding), - .severity = .Error, - .code = "cImport", - .source = "zls", - .message = try allocator.dupe(u8, pos_and_diag_iterator.rest()), - }); - } - if (server.config.enable_ast_check_diagnostics and tree.errors.len == 0) { try getAstCheckDiagnostics(server, handle, &diagnostics); } From 5d6f23b5f196faaec26182f5bd03bc3e4f9e3c30 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Fri, 16 Dec 2022 21:24:25 +0100 Subject: [PATCH 12/21] fix integer underflow in `ast.fullPtrType` (#829) --- src/ast.zig | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/ast.zig b/src/ast.zig index af7cb00..9a1709c 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -9,18 +9,15 @@ const full = Ast.full; fn fullPtrType(tree: Ast, info: full.PtrType.Components) full.PtrType { const token_tags = tree.tokens.items(.tag); - // TODO: looks like stage1 isn't quite smart enough to handle enum - // literals in some places here - const Size = std.builtin.Type.Pointer.Size; - const size: Size = switch (token_tags[info.main_token]) { + const size: std.builtin.Type.Pointer.Size = switch (token_tags[info.main_token]) { .asterisk, .asterisk_asterisk, => switch (token_tags[info.main_token + 1]) { .r_bracket, .colon => .Many, - .identifier => if (token_tags[info.main_token - 1] == .l_bracket) Size.C else .One, + .identifier => if (info.main_token != 0 and token_tags[info.main_token - 1] == .l_bracket) .C else .One, else => .One, }, - .l_bracket => Size.Slice, + .l_bracket => .Slice, else => unreachable, }; var result: full.PtrType = .{ From d679b19676dcab202512c76ac05b992e4c314065 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sat, 17 Dec 2022 09:02:31 +0100 Subject: [PATCH 13/21] remove old stage1 code artifacts (#831) --- src/Server.zig | 123 ++++++++++++-------------------------------- src/inlay_hints.zig | 19 ++++--- 2 files changed, 41 insertions(+), 101 deletions(-) diff --git a/src/Server.zig b/src/Server.zig index 0a7bc8c..2428aa4 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -1748,42 +1748,20 @@ fn registerCapability(server: *Server, writer: anytype, method: []const u8) !voi const id = try std.fmt.allocPrint(server.arena.allocator(), "register-{s}", .{method}); log.debug("Dynamically registering method '{s}'", .{method}); - if (zig_builtin.zig_backend == .stage1) { - const reg = types.RegistrationParams.Registration{ - .id = id, - .method = method, - }; - const registrations = [1]types.RegistrationParams.Registration{reg}; - const params = types.RegistrationParams{ - .registrations = ®istrations, - }; - - const respp = types.ResponseParams{ - .RegistrationParams = params, - }; - const req = types.Request{ - .id = .{ .String = id }, - .method = "client/registerCapability", - .params = respp, - }; - - try send(writer, server.arena.allocator(), req); - } else { - try send(writer, server.arena.allocator(), types.Request{ - .id = .{ .String = id }, - .method = "client/registerCapability", - .params = types.ResponseParams{ - .RegistrationParams = types.RegistrationParams{ - .registrations = &.{ - .{ - .id = id, - .method = method, - }, + try send(writer, server.arena.allocator(), types.Request{ + .id = .{ .String = id }, + .method = "client/registerCapability", + .params = types.ResponseParams{ + .RegistrationParams = types.RegistrationParams{ + .registrations = &.{ + .{ + .id = id, + .method = method, }, }, }, - }); - } + }, + }); } fn requestConfiguration(server: *Server, writer: anytype) !void { @@ -1855,18 +1833,15 @@ fn saveDocumentHandler(server: *Server, writer: anytype, id: types.RequestId, re var workspace_edit = types.WorkspaceEdit{ .changes = .{} }; try workspace_edit.changes.putNoClobber(allocator, uri, text_edits); - // NOTE: stage1 moment - const params = types.ResponseParams{ - .ApplyEdit = types.ApplyWorkspaceEditParams{ - .label = "autofix", - .edit = workspace_edit, - }, - }; - try send(writer, allocator, types.Request{ .id = .{ .String = "apply_edit" }, .method = "workspace/applyEdit", - .params = params, + .params = .{ + .ApplyEdit = .{ + .label = "autofix", + .edit = workspace_edit, + }, + }, }); } @@ -2872,56 +2847,22 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void .{ "textDocument/selectionRange", requests.SelectionRange, selectionRangeHandler }, }; - if (zig_builtin.zig_backend == .stage1) { - // Hack to avoid `return`ing in the inline for, which causes bugs. - var done: ?anyerror = null; - inline for (method_map) |method_info| { - if (done == null and std.mem.eql(u8, method, method_info[0])) { - if (method_info.len == 1) { - log.warn("method not mapped: {s}", .{method}); - done = error.HackDone; - } else if (method_info[1] != void) { - const ReqT = method_info[1]; - if (requests.fromDynamicTree(&server.arena, ReqT, tree.root)) |request_obj| { - done = error.HackDone; - done = extractErr(method_info[2](server, writer, id, request_obj)); - } else |err| { - if (err == error.MalformedJson) { - log.warn("Could not create request type {s} from JSON {s}", .{ @typeName(ReqT), json }); - } - done = err; - } - } else { - done = error.HackDone; - (method_info[2])(server, writer, id) catch |err| { - done = err; - }; - } - } - } - if (done) |err| switch (err) { - error.MalformedJson => return try respondGeneric(writer, id, null_result_response), - error.HackDone => return, - else => return err, - }; - } else { - inline for (method_map) |method_info| { - if (std.mem.eql(u8, method, method_info[0])) { - if (method_info.len == 1) { - log.warn("method not mapped: {s}", .{method}); - } else if (method_info[1] != void) { - const ReqT = method_info[1]; - const request_obj = try requests.fromDynamicTree(&server.arena, ReqT, tree.root); - method_info[2](server, writer, id, request_obj) catch |err| { - log.err("failed to process request: {s}", .{@errorName(err)}); - }; - } else { - method_info[2](server, writer, id) catch |err| { - log.err("failed to process request: {s}", .{@errorName(err)}); - }; - } - return; + inline for (method_map) |method_info| { + if (std.mem.eql(u8, method, method_info[0])) { + if (method_info.len == 1) { + log.warn("method not mapped: {s}", .{method}); + } else if (method_info[1] != void) { + const ReqT = method_info[1]; + const request_obj = try requests.fromDynamicTree(&server.arena, ReqT, tree.root); + method_info[2](server, writer, id, request_obj) catch |err| { + log.err("failed to process request: {s}", .{@errorName(err)}); + }; + } else { + method_info[2](server, writer, id) catch |err| { + log.err("failed to process request: {s}", .{@errorName(err)}); + }; } + return; } } diff --git a/src/inlay_hints.zig b/src/inlay_hints.zig index 94b66fe..c524903 100644 --- a/src/inlay_hints.zig +++ b/src/inlay_hints.zig @@ -243,16 +243,15 @@ fn writeCallNodeHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: /// HACK self-hosted has not implemented async yet fn callWriteNodeInlayHint(allocator: std.mem.Allocator, args: anytype) error{OutOfMemory}!void { - _ = allocator; - // if (zig_builtin.zig_backend == .other or zig_builtin.zig_backend == .stage1) { - // const FrameSize = @sizeOf(@Frame(writeNodeInlayHint)); - // var child_frame = try allocator.alignedAlloc(u8, std.Target.stack_align, FrameSize); - // defer allocator.free(child_frame); - // return await @asyncCall(child_frame, {}, writeNodeInlayHint, args); - // } else { - // TODO find a non recursive solution - return @call(.auto, writeNodeInlayHint, args); - // } + if (zig_builtin.zig_backend == .other or zig_builtin.zig_backend == .stage1) { + const FrameSize = @sizeOf(@Frame(writeNodeInlayHint)); + var child_frame = try allocator.alignedAlloc(u8, std.Target.stack_align, FrameSize); + defer allocator.free(child_frame); + return await @asyncCall(child_frame, {}, writeNodeInlayHint, args); + } else { + // TODO find a non recursive solution + return @call(.auto, writeNodeInlayHint, args); + } } /// iterates over the ast and writes parameter hints into `builder.hints` for every function call and builtin call From 375daba743c9c575be7081767c9de69557722ec7 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sat, 17 Dec 2022 09:03:05 +0100 Subject: [PATCH 14/21] improve behavior if zig_exe_path is not set (#830) --- src/DocumentStore.zig | 5 +---- src/Server.zig | 29 +++++++++++++++++++++-------- src/translate_c.zig | 4 ++-- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index 13dc1cb..a92f877 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -858,10 +858,7 @@ pub fn resolveCImport(self: *DocumentStore, handle: Handle, node: Ast.Node.Index /// caller owns the returned memory pub fn uriFromImportStr(self: *const DocumentStore, allocator: std.mem.Allocator, handle: Handle, import_str: []const u8) error{OutOfMemory}!?Uri { if (std.mem.eql(u8, import_str, "std")) { - const zig_lib_path = self.config.zig_lib_path orelse { - log.debug("Cannot resolve std library import, path is null.", .{}); - return null; - }; + const zig_lib_path = self.config.zig_lib_path orelse return null; const std_path = std.fs.path.resolve(allocator, &[_][]const u8{ zig_lib_path, "./std/std.zig" }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, diff --git a/src/Server.zig b/src/Server.zig index 2428aa4..c2b4a7d 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -1683,16 +1683,29 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req: log.info("{}", .{server.client_capabilities}); log.info("Using offset encoding: {s}", .{std.meta.tagName(server.offset_encoding)}); - // TODO avoid having to call getZigEnv twice - // once in init and here - const env = configuration.getZigEnv(server.allocator, server.config.zig_exe_path.?) orelse return; - defer std.json.parseFree(configuration.Env, env, .{ .allocator = server.allocator }); + if (server.config.zig_exe_path) |exe_path| blk: { + // TODO avoid having to call getZigEnv twice + // once in init and here + const env = configuration.getZigEnv(server.allocator, exe_path) orelse break :blk; + defer std.json.parseFree(configuration.Env, env, .{ .allocator = server.allocator }); - const zig_exe_version = std.SemanticVersion.parse(env.version) catch return; + const zig_exe_version = std.SemanticVersion.parse(env.version) catch break :blk; - if (zig_builtin.zig_version.order(zig_exe_version) == .gt) { - const version_mismatch_message = try std.fmt.allocPrint(server.arena.allocator(), "ZLS was built with Zig {}, but your Zig version is {s}. Update Zig to avoid unexpected behavior.", .{ zig_builtin.zig_version, env.version }); - try server.showMessage(writer, .Warning, version_mismatch_message); + if (zig_builtin.zig_version.order(zig_exe_version) == .gt) { + const version_mismatch_message = try std.fmt.allocPrint( + server.arena.allocator(), + "ZLS was built with Zig {}, but your Zig version is {s}. Update Zig to avoid unexpected behavior.", + .{ zig_builtin.zig_version, env.version }, + ); + try server.showMessage(writer, .Warning, version_mismatch_message); + } + } else { + try server.showMessage( + writer, + .Warning, + \\ZLS failed to find Zig. Please add Zig to your PATH or set the zig_exe_path config option in your zls.json. + , + ); } } diff --git a/src/translate_c.zig b/src/translate_c.zig index d58dca3..6c9f864 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -162,11 +162,11 @@ pub fn translate(allocator: std.mem.Allocator, config: Config, include_dirs: []c }; const base_args = &[_][]const u8{ - config.zig_exe_path.?, + config.zig_exe_path orelse return null, "translate-c", "--enable-cache", "--zig-lib-dir", - config.zig_lib_path.?, + config.zig_lib_path orelse return null, "--cache-dir", config.global_cache_path.?, "-lc", From e09c773005802a790ef22b51ab2dabc607079508 Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Sat, 17 Dec 2022 14:27:51 -0800 Subject: [PATCH 15/21] analysis.zig getPositionContext() - check for null closes #754 and #832 this pr just adds `.label => {}` to the switch as suggested by @nullptrdevs, thereby preventing the null unwrap. i checked that zls no longer crashes when positioning the cursor on Server.zig:2287:41 which is this line: `break :blk .{ .WorkspaceEdit = edits };` ^ cursor here previously crashed zls --- src/analysis.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/analysis.zig b/src/analysis.zig index 09a6ad3..7eadf06 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -1612,6 +1612,7 @@ pub fn getPositionContext(allocator: std.mem.Allocator, text: []const u8, doc_in .field_access => {}, .other => {}, .global_error_set => {}, + .label => {}, else => curr_ctx.ctx = .{ .field_access = tokenLocAppend(curr_ctx.ctx.loc().?, tok), }, From 427620742afd4e6d8646148db249960e3332c061 Mon Sep 17 00:00:00 2001 From: Steven Kabbes Date: Sun, 18 Dec 2022 12:45:36 -0700 Subject: [PATCH 16/21] track builtin API changes from zig/master see: https://github.com/ziglang/zig/pull/13930 In this PR, std.builtin.Type.field_type is renamed to type And there is discussion that all Enum layouts are .Auto --- src/Server.zig | 10 +++++----- src/configuration.zig | 6 +++--- src/requests.zig | 9 +++------ 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Server.zig b/src/Server.zig index c2b4a7d..874d23a 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -2752,18 +2752,18 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void inline for (std.meta.fields(Config)) |field, index| { const value = result.items[index]; - const ft = if (@typeInfo(field.field_type) == .Optional) - @typeInfo(field.field_type).Optional.child + const ft = if (@typeInfo(field.type) == .Optional) + @typeInfo(field.type).Optional.child else - field.field_type; + field.type; const ti = @typeInfo(ft); if (value != .Null) { - const new_value: field.field_type = switch (ft) { + const new_value: field.type = switch (ft) { []const u8 => switch (value) { .String => |s| blk: { if (s.len == 0) { - if (field.field_type == ?[]const u8) { + if (field.type == ?[]const u8) { break :blk null; } else { break :blk s; diff --git a/src/configuration.zig b/src/configuration.zig index 9204e2b..8006941 100644 --- a/src/configuration.zig +++ b/src/configuration.zig @@ -198,9 +198,9 @@ fn getConfigurationType() type { var fields: [config_info.Struct.fields.len]std.builtin.Type.StructField = undefined; for (config_info.Struct.fields) |field, i| { fields[i] = field; - if (@typeInfo(field.field_type) != .Optional) { - fields[i].field_type = @Type(std.builtin.Type{ - .Optional = .{ .child = field.field_type }, + if (@typeInfo(field.type) != .Optional) { + fields[i].type = @Type(std.builtin.Type{ + .Optional = .{ .child = field.type }, }); } } diff --git a/src/requests.zig b/src/requests.zig index b728161..f0cdb2b 100644 --- a/src/requests.zig +++ b/src/requests.zig @@ -45,10 +45,10 @@ fn fromDynamicTreeInternal(arena: *std.heap.ArenaAllocator, value: std.json.Valu var err = false; inline for (std.meta.fields(T)) |field| { - const is_exists = field.field_type == Exists; + const is_exists = field.type == Exists; - const is_optional = comptime std.meta.trait.is(.Optional)(field.field_type); - const actual_type = if (is_optional) std.meta.Child(field.field_type) else field.field_type; + const is_optional = comptime std.meta.trait.is(.Optional)(field.type); + const actual_type = if (is_optional) std.meta.Child(field.type) else field.type; const is_struct = comptime std.meta.trait.is(.Struct)(actual_type); const is_default = comptime if (is_struct) std.meta.trait.hasDecls(actual_type, .{ "default", "value_type" }) else false; @@ -107,9 +107,6 @@ fn fromDynamicTreeInternal(arena: *std.heap.ArenaAllocator, value: std.json.Valu out.* = value; } else if (comptime std.meta.trait.is(.Enum)(T)) { const info = @typeInfo(T).Enum; - if (info.layout != .Auto) - @compileError("Only auto layout enums are allowed"); - const TagType = info.tag_type; if (value != .Integer) return error.MalformedJson; out.* = std.meta.intToEnum( From 23ef018521714b988cfca6c34a4832358e3e1309 Mon Sep 17 00:00:00 2001 From: Steven Kabbes Date: Sun, 18 Dec 2022 18:05:51 -0700 Subject: [PATCH 17/21] build: bump zig min version --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 29926d9..370093f 100644 --- a/build.zig +++ b/build.zig @@ -6,7 +6,7 @@ const zls_version = std.builtin.Version{ .major = 0, .minor = 11, .patch = 0 }; pub fn build(b: *std.build.Builder) !void { const current_zig = builtin.zig_version; - const min_zig = std.SemanticVersion.parse("0.11.0-dev.780+6378644d4") catch return; // Changes to @call + const min_zig = std.SemanticVersion.parse("0.11.0-dev.874+40ed6ae84") catch return; // Changes to builtin.Type API if (current_zig.order(min_zig).compare(.lt)) @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); const target = b.standardTargetOptions(.{}); From c355a54dd1072fafa1c4b37d6f1d2b5b31255475 Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Mon, 19 Dec 2022 16:01:08 -0500 Subject: [PATCH 18/21] Fix raw, responseless returns on willSaveWaitUntil (#833) * Fix raw, responseless returns on willSaveWaitUntil * Make this code pretty --- src/Server.zig | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Server.zig b/src/Server.zig index 874d23a..588af8e 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -1879,20 +1879,28 @@ fn willSaveWaitUntilHandler(server: *Server, writer: anytype, id: types.RequestI const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); - if (!server.config.enable_ast_check_diagnostics) return; - if (!server.config.enable_autofix) return; - const allocator = server.arena.allocator(); - const uri = req.params.textDocument.uri; - const handle = server.document_store.getHandle(uri) orelse return; - if (handle.tree.errors.len != 0) return; + b: { + if (!server.config.enable_ast_check_diagnostics or !server.config.enable_autofix) + break :b; - var text_edits = try server.autofix(allocator, handle); + const uri = req.params.textDocument.uri; + + const handle = server.document_store.getHandle(uri) orelse break :b; + if (handle.tree.errors.len != 0) break :b; + + var text_edits = try server.autofix(allocator, handle); + + return try send(writer, allocator, types.Response{ + .id = id, + .result = .{ .TextEdits = try text_edits.toOwnedSlice(allocator) }, + }); + } return try send(writer, allocator, types.Response{ .id = id, - .result = .{ .TextEdits = try text_edits.toOwnedSlice(allocator) }, + .result = .{ .TextEdits = &.{} }, }); } From 44b6c4dae4ace789c5a3ebf9fdbeb65db8f1cf30 Mon Sep 17 00:00:00 2001 From: Eric Puentes Date: Thu, 22 Dec 2022 22:25:56 -0500 Subject: [PATCH 19/21] fix: avoid finding references in the current file twice (#846) --- src/references.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/references.zig b/src/references.zig index 3da4e88..e04df2f 100644 --- a/src/references.zig +++ b/src/references.zig @@ -497,6 +497,8 @@ pub fn symbolReferences( for (dependencies.keys()) |uri| { const handle = store.getHandle(uri) orelse continue; + if (std.mem.eql(u8, handle.uri, curr_handle.uri)) continue; + try symbolReferencesInternal(&builder, 0, handle, true); } }, From 4d7b95e1e2c6231287a2687cc42025feeb53f5eb Mon Sep 17 00:00:00 2001 From: rimuspp Date: Thu, 22 Dec 2022 22:26:29 -0500 Subject: [PATCH 20/21] generated master data for new builtins (#845) --- src/data/master.zig | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/data/master.zig b/src/data/master.zig index c5f45b4..bc38179 100644 --- a/src/data/master.zig +++ b/src/data/master.zig @@ -506,6 +506,49 @@ pub const builtins = [_]Builtin{ "comptime name: []u8", }, }, + .{ + .name = "@cVaArg", + .signature = "@cVaArg(operand: *std.builtin.VaList, comptime T: type) T", + .snippet = "@cVaArg(${1:operand: *std.builtin.VaList}, ${2:comptime T: type})", + .documentation = + \\Implements the C macro `va_arg`. + , + .arguments = &.{ + "operand: *std.builtin.VaList", + "comptime T: type", + }, + }, + .{ + .name = "@cVaCopy", + .signature = "@cVaCopy(src: *std.builtin.VaList) std.builtin.VaList", + .snippet = "@cVaCopy(${1:src: *std.builtin.VaList})", + .documentation = + \\Implements the C macro `va_copy`. + , + .arguments = &.{ + "src: *std.builtin.VaList", + }, + }, + .{ + .name = "@cVaEnd", + .signature = "@cVaEnd(src: *std.builtin.VaList) void", + .snippet = "@cVaEnd(${1:src: *std.builtin.VaList})", + .documentation = + \\Implements the C macro `va_end`. + , + .arguments = &.{ + "src: *std.builtin.VaList", + }, + }, + .{ + .name = "@cVaStart", + .signature = "@cVaStart() std.builtin.VaList", + .snippet = "@cVaStart()", + .documentation = + \\Implements the C macro `va_start`. Only valid inside a variadic function. + , + .arguments = &.{}, + }, .{ .name = "@divExact", .signature = "@divExact(numerator: T, denominator: T) T", From f6c15ac10c6f43ed1416911a832b4e4f1f958e23 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 22 Dec 2022 19:27:38 -0800 Subject: [PATCH 21/21] semantic_tokens: Fix handleComments not evaluating the last byte (#844) Fixes #842 --- src/semantic_tokens.zig | 10 +++++----- tests/lsp_features/semantic_tokens.zig | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/semantic_tokens.zig b/src/semantic_tokens.zig index 48c8f20..9e2cf0b 100644 --- a/src/semantic_tokens.zig +++ b/src/semantic_tokens.zig @@ -141,16 +141,16 @@ const Builder = struct { const source = self.handle.tree.source; var i: usize = from; - while (i < to - 1) : (i += 1) { + while (i < to) : (i += 1) { // Skip multi-line string literals if (source[i] == '\\' and source[i + 1] == '\\') { - while (i < to - 1 and source[i] != '\n') : (i += 1) {} + while (i < to and source[i] != '\n') : (i += 1) {} continue; } // Skip normal string literals if (source[i] == '"') { i += 1; - while (i < to - 1 and + while (i < to and source[i] != '\n' and !(source[i] == '"' and source[i - 1] != '\\')) : (i += 1) {} @@ -159,7 +159,7 @@ const Builder = struct { // Skip char literals if (source[i] == '\'') { i += 1; - while (i < to - 1 and + while (i < to and source[i] != '\n' and !(source[i] == '\'' and source[i - 1] != '\\')) : (i += 1) {} @@ -174,7 +174,7 @@ const Builder = struct { if (i + 2 < to and (source[i + 2] == '!' or source[i + 2] == '/')) mods.documentation = true; - while (i < to - 1 and source[i] != '\n') : (i += 1) {} + while (i < to and source[i] != '\n') : (i += 1) {} const length = offsets.locLength(self.handle.tree.source, .{ .start = comment_start, .end = i }, self.encoding); try self.addDirect(TokenType.comment, mods, comment_start, length); diff --git a/tests/lsp_features/semantic_tokens.zig b/tests/lsp_features/semantic_tokens.zig index 113589e..ba906a9 100644 --- a/tests/lsp_features/semantic_tokens.zig +++ b/tests/lsp_features/semantic_tokens.zig @@ -22,6 +22,16 @@ test "semantic tokens" { // TODO more tests } +test "semantic tokens - comments" { + try testSemanticTokens( + \\//!─ + , + &.{ 0, 0, 4, 8, 128 }, + ); + + // TODO more tests +} + const file_uri = switch (builtin.os.tag) { .windows => "file:///C:/test.zig", else => "file:///test.zig",