From 6019eff13e92412e441770b9488def38c3e127a2 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Thu, 26 Jan 2023 22:04:49 +0100 Subject: [PATCH 01/12] Fuzzer fixes (#940) * better handling of container_decl_arg_trailing * ignore semantic token when moving backwards * use custom ast functions instead of from std --- src/ast.zig | 27 +++++++++++++------------- src/semantic_tokens.zig | 20 ++++++------------- tests/lsp_features/semantic_tokens.zig | 12 ++++++++++++ 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/ast.zig b/src/ast.zig index cfbf935..71cdf61 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -254,29 +254,29 @@ pub fn forFull(tree: Ast, node: Node.Index) full.While { pub fn fullPtrType(tree: Ast, node: Node.Index) ?full.PtrType { return switch (tree.nodes.items(.tag)[node]) { - .ptr_type_aligned => tree.ptrTypeAligned(node), - .ptr_type_sentinel => tree.ptrTypeSentinel(node), - .ptr_type => tree.ptrType(node), - .ptr_type_bit_range => tree.ptrTypeBitRange(node), + .ptr_type_aligned => ptrTypeAligned(tree, node), + .ptr_type_sentinel => ptrTypeSentinel(tree, node), + .ptr_type => ptrTypeSimple(tree, node), + .ptr_type_bit_range => ptrTypeBitRange(tree, node), else => null, }; } pub fn fullIf(tree: Ast, node: Node.Index) ?full.If { return switch (tree.nodes.items(.tag)[node]) { - .if_simple => tree.ifSimple(node), - .@"if" => tree.ifFull(node), + .if_simple => ifSimple(tree, node), + .@"if" => ifFull(tree, node), else => null, }; } pub fn fullWhile(tree: Ast, node: Node.Index) ?full.While { return switch (tree.nodes.items(.tag)[node]) { - .while_simple => tree.whileSimple(node), - .while_cont => tree.whileCont(node), - .@"while" => tree.whileFull(node), - .for_simple => tree.forSimple(node), - .@"for" => tree.forFull(node), + .while_simple => whileSimple(tree, node), + .while_cont => whileCont(tree, node), + .@"while" => whileFull(tree, node), + .for_simple => forSimple(tree, node), + .@"for" => forFull(tree, node), else => null, }; } @@ -543,7 +543,9 @@ pub fn lastToken(tree: Ast, node: Ast.Node.Index) Ast.TokenIndex { n = tree.extra_data[cases.end - 1]; // last case } }, - .container_decl_arg => { + .container_decl_arg, + .container_decl_arg_trailing, + => { const members = tree.extraData(datas[n].rhs, Node.SubRange); if (members.end - members.start == 0) { end_offset += 3; // for the rparen + lbrace + rbrace @@ -567,7 +569,6 @@ pub fn lastToken(tree: Ast, node: Ast.Node.Index) Ast.TokenIndex { }, .array_init_comma, .struct_init_comma, - .container_decl_arg_trailing, .switch_comma, => { if (datas[n].rhs != 0) { diff --git a/src/semantic_tokens.zig b/src/semantic_tokens.zig index 0484971..254f551 100644 --- a/src/semantic_tokens.zig +++ b/src/semantic_tokens.zig @@ -74,9 +74,7 @@ const Builder = struct { const starts = tree.tokens.items(.start); const next_start = starts[token]; - if (next_start < self.previous_position) { - return error.MovedBackwards; - } + if (next_start < self.previous_position) return; if (self.previous_token) |prev| { // Highlight gaps between AST nodes. These can contain comments or malformed code. @@ -182,6 +180,8 @@ const Builder = struct { } fn addDirect(self: *Builder, tok_type: TokenType, tok_mod: TokenModifiers, start: usize, length: usize) !void { + if (start < self.previous_position) return; + const text = self.handle.tree.source[self.previous_position..start]; const delta = offsets.indexToPosition(text, text.len, self.encoding); @@ -261,13 +261,8 @@ fn colorIdentifierBasedOnType(builder: *Builder, type_node: analysis.TypeWithHan } } -const WriteTokensError = error{ - OutOfMemory, - MovedBackwards, -}; - /// HACK self-hosted has not implemented async yet -fn callWriteNodeTokens(allocator: std.mem.Allocator, args: anytype) WriteTokensError!void { +fn callWriteNodeTokens(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(writeNodeTokens)); var child_frame = try allocator.alignedAlloc(u8, std.Target.stack_align, FrameSize); @@ -280,7 +275,7 @@ fn callWriteNodeTokens(allocator: std.mem.Allocator, args: anytype) WriteTokensE } } -fn writeNodeTokens(builder: *Builder, maybe_node: ?Ast.Node.Index) WriteTokensError!void { +fn writeNodeTokens(builder: *Builder, maybe_node: ?Ast.Node.Index) error{OutOfMemory}!void { const node = maybe_node orelse return; const handle = builder.handle; @@ -1010,10 +1005,7 @@ pub fn writeAllSemanticTokens( // reverse the ast from the root declarations for (handle.tree.rootDecls()) |child| { - writeNodeTokens(&builder, child) catch |err| switch (err) { - error.MovedBackwards => break, - else => |e| return e, - }; + try writeNodeTokens(&builder, child); } try builder.finish(); return builder.toOwnedSlice(); diff --git a/tests/lsp_features/semantic_tokens.zig b/tests/lsp_features/semantic_tokens.zig index 459a0ea..f36657d 100644 --- a/tests/lsp_features/semantic_tokens.zig +++ b/tests/lsp_features/semantic_tokens.zig @@ -32,6 +32,18 @@ test "semantic tokens - comments" { // TODO more tests } +test "semantic tokens - string literals" { + // https://github.com/zigtools/zls/issues/921 + try testSemanticTokens( + \\" + \\"",// + \\"": + , + // no idea if this output is correct but at least it doesn't crash + &.{ 1, 3, 3, 8, 0, 1, 0, 2, 4, 0, 0, 0, 2, 9, 0 }, + ); +} + const file_uri = switch (builtin.os.tag) { .windows => "file:///C:/test.zig", else => "file:///test.zig", From e055f9d15cf3ccaae5df8bd3377357e63db688ab Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Thu, 26 Jan 2023 22:57:07 +0100 Subject: [PATCH 02/12] remove unused capacity from Ast and DocumentStore (#941) --- src/DocumentStore.zig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index 0edaf1a..1384363 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -622,9 +622,19 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) erro var tree = try std.zig.parse(self.allocator, text); errdefer tree.deinit(self.allocator); + var nodes = tree.nodes.toMultiArrayList(); + try nodes.setCapacity(self.allocator, nodes.len); + tree.nodes = nodes.slice(); + + var tokens = tree.tokens.toMultiArrayList(); + try tokens.setCapacity(self.allocator, tokens.len); + tree.tokens = tokens.slice(); + var document_scope = try analysis.makeDocumentScope(self.allocator, tree); errdefer document_scope.deinit(self.allocator); + try document_scope.scopes.setCapacity(self.allocator, document_scope.scopes.len); + break :blk Handle{ .open = open, .uri = duped_uri, From 2857237f7478095cb1fcd842777efbf73886e89a Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Fri, 27 Jan 2023 15:59:11 +0100 Subject: [PATCH 03/12] add colon to inlay hint label (#944) --- src/inlay_hints.zig | 2 +- tests/lsp_features/inlay_hints.zig | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/inlay_hints.zig b/src/inlay_hints.zig index ef3fc23..4f88aa9 100644 --- a/src/inlay_hints.zig +++ b/src/inlay_hints.zig @@ -55,7 +55,7 @@ const Builder = struct { try self.hints.append(self.arena, .{ .position = position, - .label = .{ .string = label }, + .label = .{ .string = try std.fmt.allocPrint(self.arena, "{s}:", .{label}) }, .kind = types.InlayHintKind.Parameter, .tooltip = .{ .MarkupContent = .{ .kind = self.hover_kind, diff --git a/tests/lsp_features/inlay_hints.zig b/tests/lsp_features/inlay_hints.zig index 8660aa8..8969da8 100644 --- a/tests/lsp_features/inlay_hints.zig +++ b/tests/lsp_features/inlay_hints.zig @@ -120,7 +120,10 @@ fn testInlayHints(source: []const u8) !void { for (hints) |hint| { if (position.line != hint.position.line or position.character != hint.position.character) continue; - const actual_label = hint.label[0..hint.label.len]; + if(!std.mem.endsWith(u8, hint.label, ":")) { + try error_builder.msgAtLoc("label `{s}` must end with a colon!", new_loc, .err, .{ hint.label }); + } + const actual_label = hint.label[0..hint.label.len - 1]; if (!std.mem.eql(u8, expected_label, actual_label)) { try error_builder.msgAtLoc("expected label `{s}` here but got `{s}`!", new_loc, .err, .{ expected_label, actual_label }); From 805e8389cb187ee84f3999d98168a51f75029472 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sat, 28 Jan 2023 19:47:03 +0100 Subject: [PATCH 04/12] Update README.md --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 93ac9da..d6d5deb 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ You can replace `master` with a specific zig version like `0.10.0`. Which versio ### Configuration Options You can configure zls by editing your `zls.json` configuration file. -Running `zls --show-config-path` will a path to an already existing `zls.json` or a path to the local configuration folder instead. +Running `zls --show-config-path` will show a path to an already existing `zls.json` or a path to the local configuration folder instead. zls will look for a `zls.json` configuration file in multiple locations with the following priority: - In the local configuration folder of your OS (as provided by [known-folders](https://github.com/ziglibs/known-folders/blob/master/RESOURCES.md#folder-list)) @@ -112,8 +112,7 @@ When `value` is present, the option will be passed the same as in `zig build -Dn ## Features -`zls` supports most language features, including simple type function support, using namespace, payload capture type resolution, custom packages, `cImport` and others. -Currently there is no support for compile time evaluation. +`zls` supports most language features, including simple type function support, using namespace, payload capture type resolution, custom packages, cImport and others. Support for comptime and semantic analysis is Work-in-Progress. The following LSP features are supported: - Completions @@ -123,8 +122,11 @@ The following LSP features are supported: - Find references - Rename symbol - Formatting using `zig fmt` -- Semantic token highlighting (implemented by a few clients including VS Code, kak and emacs lsp-mode) -- Inlay hints (implemented by VS Code) +- Semantic token highlighting +- Inlay hints +- Code actions +- Selection ranges +- Folding regions ## Related Projects From 3080a5d315b3b0ecf2c648e97c56513e8491a527 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sat, 28 Jan 2023 20:04:02 +0100 Subject: [PATCH 05/12] fix config_gen --- src/config_gen/config_gen.zig | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/config_gen/config_gen.zig b/src/config_gen/config_gen.zig index 0c2de59..d7a1f8c 100644 --- a/src/config_gen/config_gen.zig +++ b/src/config_gen/config_gen.zig @@ -531,7 +531,14 @@ fn collectBuiltinData(allocator: std.mem.Allocator, version: []const u8, langref switch (tokenizer.next().id) { .Separator => { std.debug.assert(tokenizer.next().id == .TagContent); - std.debug.assert(tokenizer.next().id == .BracketClose); + switch (tokenizer.next().id) { + .Separator => { + std.debug.assert(tokenizer.next().id == .TagContent); + std.debug.assert(tokenizer.next().id == .BracketClose); + }, + .BracketClose => {}, + else => unreachable, + } }, .BracketClose => {}, else => unreachable, @@ -865,7 +872,7 @@ const Response = union(enum) { fn httpGET(allocator: std.mem.Allocator, uri: std.Uri) !Response { var client = std.http.Client{ .allocator = allocator }; - defer client.deinit(allocator); + defer client.deinit(); try client.ca_bundle.rescan(allocator); var request = try client.request(uri, .{}, .{}); From eac61ba8be1be13d294db2f50c52a6ebbbadf903 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sat, 28 Jan 2023 20:04:21 +0100 Subject: [PATCH 06/12] update data files --- src/data/0.10.1.zig | 2066 +++++++++++++++++++++++++++++++++++++++++++ src/data/data.zig | 1 + src/data/master.zig | 4 +- src/shared.zig | 1 + 4 files changed, 2070 insertions(+), 2 deletions(-) create mode 100644 src/data/0.10.1.zig diff --git a/src/data/0.10.1.zig b/src/data/0.10.1.zig new file mode 100644 index 0000000..57745c2 --- /dev/null +++ b/src/data/0.10.1.zig @@ -0,0 +1,2066 @@ +//! DO NOT EDIT +//! If you want to update this file run: +//! `zig build gen -- --generate-version-data 0.10.1` (requires an internet connection) +//! GENERATED BY src/config_gen/config_gen.zig + +const Builtin = struct { + name: []const u8, + signature: []const u8, + snippet: []const u8, + documentation: []const u8, + arguments: []const []const u8, +}; + +pub const builtins = [_]Builtin{ + .{ + .name = "@addrSpaceCast", + .signature = "@addrSpaceCast(comptime addrspace: std.builtin.AddressSpace, ptr: anytype) anytype", + .snippet = "@addrSpaceCast(${1:comptime addrspace: std.builtin.AddressSpace}, ${2:ptr: anytype})", + .documentation = + \\Converts a pointer from one address space to another. Depending on the current target and address spaces, this cast may be a no-op, a complex operation, or illegal. If the cast is legal, then the resulting pointer points to the same memory location as the pointer operand. It is always valid to cast a pointer between the same address spaces. + , + .arguments = &.{ + "comptime addrspace: std.builtin.AddressSpace", + "ptr: anytype", + }, + }, + .{ + .name = "@addWithOverflow", + .signature = "@addWithOverflow(comptime T: type, a: T, b: T, result: *T) bool", + .snippet = "@addWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:b: T}, ${4:result: *T})", + .documentation = + \\Performs `result.* = a + b`. If overflow or underflow occurs, stores the overflowed bits in `result` and returns `true`. If no overflow or underflow occurs, returns `false`. + , + .arguments = &.{ + "comptime T: type", + "a: T", + "b: T", + "result: *T", + }, + }, + .{ + .name = "@alignCast", + .signature = "@alignCast(comptime alignment: u29, ptr: anytype) anytype", + .snippet = "@alignCast(${1:comptime alignment: u29}, ${2:ptr: anytype})", + .documentation = + \\`ptr` can be `*T`, `?*T`, or `[]T`. It returns the same type as `ptr` except with the alignment adjusted to the new value. + \\ + \\A [pointer alignment safety check](https://ziglang.org/documentation/0.10.1/#Incorrect-Pointer-Alignment) is added to the generated code to make sure the pointer is aligned as promised. + , + .arguments = &.{ + "comptime alignment: u29", + "ptr: anytype", + }, + }, + .{ + .name = "@alignOf", + .signature = "@alignOf(comptime T: type) comptime_int", + .snippet = "@alignOf(${1:comptime T: type})", + .documentation = + \\This function returns the number of bytes that this type should be aligned to for the current target to match the C ABI. When the child type of a pointer has this alignment, the alignment can be omitted from the type. + \\ + \\```zig + \\const assert = @import("std").debug.assert; + \\comptime { + \\ assert(*u32 == *align(@alignOf(u32)) u32); + \\} + \\``` + \\The result is a target-specific compile time constant. It is guaranteed to be less than or equal to [@sizeOf(T)](https://ziglang.org/documentation/0.10.1/#sizeOf). + , + .arguments = &.{ + "comptime T: type", + }, + }, + .{ + .name = "@as", + .signature = "@as(comptime T: type, expression) T", + .snippet = "@as(${1:comptime T: type}, ${2:expression})", + .documentation = + \\Performs [Type Coercion](https://ziglang.org/documentation/0.10.1/#Type-Coercion). This cast is allowed when the conversion is unambiguous and safe, and is the preferred way to convert between types, whenever possible. + , + .arguments = &.{ + "comptime T: type", + "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)})", + .documentation = + \\`@asyncCall` performs an `async` call on a function pointer, which may or may not be an [async function](https://ziglang.org/documentation/0.10.1/#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/0.10.1/#frameSize). To provide a too-small buffer invokes safety-checked [Undefined Behavior](https://ziglang.org/documentation/0.10.1/#Undefined-Behavior). + \\ + \\`result_ptr` is optional ([null](https://ziglang.org/documentation/0.10.1/#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/0.10.1/#Async-and-Await) completes. Any result location provided to `await` will copy the result from `result_ptr`. + \\ + \\```zig + \\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)", + }, + }, + .{ + .name = "@atomicLoad", + .signature = "@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: builtin.AtomicOrder) T", + .snippet = "@atomicLoad(${1:comptime T: type}, ${2:ptr: *const T}, ${3:comptime ordering: builtin.AtomicOrder})", + .documentation = + \\This builtin function atomically dereferences a pointer and returns the value. + \\ + \\`T` must be a pointer, a `bool`, a float, an integer or an enum. + , + .arguments = &.{ + "comptime T: type", + "ptr: *const T", + "comptime ordering: builtin.AtomicOrder", + }, + }, + .{ + .name = "@atomicRmw", + .signature = "@atomicRmw(comptime T: type, ptr: *T, comptime op: builtin.AtomicRmwOp, operand: T, comptime ordering: builtin.AtomicOrder) T", + .snippet = "@atomicRmw(${1:comptime T: type}, ${2:ptr: *T}, ${3:comptime op: builtin.AtomicRmwOp}, ${4:operand: T}, ${5:comptime ordering: builtin.AtomicOrder})", + .documentation = + \\This builtin function atomically modifies memory and then returns the previous value. + \\ + \\`T` must be a pointer, a `bool`, a float, an integer or an enum. + \\ + \\Supported operations: + \\ + \\ - `.Xchg` - stores the operand unmodified. Supports enums, integers and floats. + \\ - `.Add` - for integers, twos complement wraparound addition. Also supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats). + \\ - `.Sub` - for integers, twos complement wraparound subtraction. Also supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats). + \\ - `.And` - bitwise and + \\ - `.Nand` - bitwise nand + \\ - `.Or` - bitwise or + \\ - `.Xor` - bitwise xor + \\ - `.Max` - stores the operand if it is larger. Supports integers and floats. + \\ - `.Min` - stores the operand if it is smaller. Supports integers and floats. + , + .arguments = &.{ + "comptime T: type", + "ptr: *T", + "comptime op: builtin.AtomicRmwOp", + "operand: T", + "comptime ordering: builtin.AtomicOrder", + }, + }, + .{ + .name = "@atomicStore", + .signature = "@atomicStore(comptime T: type, ptr: *T, value: T, comptime ordering: builtin.AtomicOrder) void", + .snippet = "@atomicStore(${1:comptime T: type}, ${2:ptr: *T}, ${3:value: T}, ${4:comptime ordering: builtin.AtomicOrder})", + .documentation = + \\This builtin function atomically stores a value. + \\ + \\`T` must be a pointer, a `bool`, a float, an integer or an enum. + , + .arguments = &.{ + "comptime T: type", + "ptr: *T", + "value: T", + "comptime ordering: builtin.AtomicOrder", + }, + }, + .{ + .name = "@bitCast", + .signature = "@bitCast(comptime DestType: type, value: anytype) DestType", + .snippet = "@bitCast(${1:comptime DestType: type}, ${2:value: anytype})", + .documentation = + \\Converts a value of one type to another type. + \\ + \\Asserts that `@sizeOf(@TypeOf(value)) == @sizeOf(DestType)`. + \\ + \\Asserts that `@typeInfo(DestType) != .Pointer`. Use `@ptrCast` or `@intToPtr` if you need this. + \\ + \\Can be used for these things for example: + \\ + \\ - Convert `f32` to `u32` bits + \\ - Convert `i32` to `u32` preserving twos complement + \\Works at compile-time if `value` is known at compile time. It's a compile error to bitcast a value of undefined layout; this means that, besides the restriction from types which possess dedicated casting builtins (enums, pointers, error sets), bare structs, error unions, slices, optionals, and any other type without a well-defined memory layout, also cannot be used in this operation. + , + .arguments = &.{ + "comptime DestType: type", + "value: anytype", + }, + }, + .{ + .name = "@bitOffsetOf", + .signature = "@bitOffsetOf(comptime T: type, comptime field_name: []const u8) comptime_int", + .snippet = "@bitOffsetOf(${1:comptime T: type}, ${2:comptime field_name: []const u8})", + .documentation = + \\Returns the bit offset of a field relative to its containing struct. + \\ + \\For non [packed structs](https://ziglang.org/documentation/0.10.1/#packed-struct), this will always be divisible by `8`. For packed structs, non-byte-aligned fields will share a byte offset, but they will have different bit offsets. + , + .arguments = &.{ + "comptime T: type", + "comptime field_name: []const u8", + }, + }, + .{ + .name = "@boolToInt", + .signature = "@boolToInt(value: bool) u1", + .snippet = "@boolToInt(${1:value: bool})", + .documentation = + \\Converts `true` to `@as(u1, 1)` and `false` to `@as(u1, 0)`. + \\ + \\If the value is known at compile-time, the return type is `comptime_int` instead of `u1`. + , + .arguments = &.{ + "value: bool", + }, + }, + .{ + .name = "@bitSizeOf", + .signature = "@bitSizeOf(comptime T: type) comptime_int", + .snippet = "@bitSizeOf(${1:comptime T: type})", + .documentation = + \\This function returns the number of bits it takes to store `T` in memory if the type were a field in a packed struct/union. The result is a target-specific compile time constant. + \\ + \\This function measures the size at runtime. For types that are disallowed at runtime, such as `comptime_int` and `type`, the result is `0`. + , + .arguments = &.{ + "comptime T: type", + }, + }, + .{ + .name = "@breakpoint", + .signature = "@breakpoint()", + .snippet = "@breakpoint()", + .documentation = + \\This function inserts a platform-specific debug trap instruction which causes debuggers to break there. + \\ + \\This function is only valid within function scope. + , + .arguments = &.{}, + }, + .{ + .name = "@mulAdd", + .signature = "@mulAdd(comptime T: type, a: T, b: T, c: T) T", + .snippet = "@mulAdd(${1:comptime T: type}, ${2:a: T}, ${3:b: T}, ${4:c: T})", + .documentation = + \\Fused multiply-add, similar to `(a * b) + c`, except only rounds once, and is thus more accurate. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats. + , + .arguments = &.{ + "comptime T: type", + "a: T", + "b: T", + "c: T", + }, + }, + .{ + .name = "@byteSwap", + .signature = "@byteSwap(operand: anytype) T", + .snippet = "@byteSwap(${1:operand: anytype})", + .documentation = + \\`@TypeOf(operand)` must be an integer type or an integer vector type with bit count evenly divisible by 8. + \\ + \\`operand` may be an [integer](https://ziglang.org/documentation/0.10.1/#Integers) or [vector](https://ziglang.org/documentation/0.10.1/#Vectors). + \\ + \\Swaps the byte order of the integer. This converts a big endian integer to a little endian integer, and converts a little endian integer to a big endian integer. + \\ + \\Note that for the purposes of memory layout with respect to endianness, the integer type should be related to the number of bytes reported by [@sizeOf](https://ziglang.org/documentation/0.10.1/#sizeOf) bytes. This is demonstrated with `u24`. `@sizeOf(u24) == 4`, which means that a `u24` stored in memory takes 4 bytes, and those 4 bytes are what are swapped on a little vs big endian system. On the other hand, if `T` is specified to be `u24`, then only 3 bytes are reversed. + , + .arguments = &.{ + "operand: anytype", + }, + }, + .{ + .name = "@bitReverse", + .signature = "@bitReverse(integer: anytype) T", + .snippet = "@bitReverse(${1:integer: anytype})", + .documentation = + \\`@TypeOf(anytype)` accepts any integer type or integer vector type. + \\ + \\Reverses the bitpattern of an integer value, including the sign bit if applicable. + \\ + \\For example 0b10110110 (`u8 = 182`, `i8 = -74`) becomes 0b01101101 (`u8 = 109`, `i8 = 109`). + , + .arguments = &.{ + "integer: anytype", + }, + }, + .{ + .name = "@offsetOf", + .signature = "@offsetOf(comptime T: type, comptime field_name: []const u8) comptime_int", + .snippet = "@offsetOf(${1:comptime T: type}, ${2:comptime field_name: []const u8})", + .documentation = + \\Returns the byte offset of a field relative to its containing struct. + , + .arguments = &.{ + "comptime T: type", + "comptime field_name: []const u8", + }, + }, + .{ + .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})", + .documentation = + \\Calls a function, in the same way that invoking an expression with parentheses does: + \\ + \\```zig + \\const expect = @import("std").testing.expect; + \\test "noinline function call" { + \\ try expect(@call(.{}, add, .{3, 9}) == 12); + \\} + \\fn add(a: i32, b: i32) i32 { + \\ return a + b; + \\} + \\``` + \\`@call` allows more flexibility than normal function call syntax does. The `CallOptions` struct is reproduced here: + \\ + \\```zig + \\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, + \\ }; + \\}; + \\``` + , + .arguments = &.{ + "options: std.builtin.CallOptions", + "function: anytype", + "args: anytype", + }, + }, + .{ + .name = "@cDefine", + .signature = "@cDefine(comptime name: []u8, value)", + .snippet = "@cDefine(${1:comptime name: []u8}, ${2:value})", + .documentation = + \\This function can only occur inside `@cImport`. + \\ + \\This appends + \\`#define $name $value`to the `@cImport` temporary buffer. + \\ + \\To define without a value, like this: + \\ + \\`#define _GNU_SOURCE`Use the void value, like this: + \\ + \\`@cDefine("_GNU_SOURCE", {})` + , + .arguments = &.{ + "comptime name: []u8", + "value", + }, + }, + .{ + .name = "@cImport", + .signature = "@cImport(expression) type", + .snippet = "@cImport(${1:expression})", + .documentation = + \\This function parses C code and imports the functions, types, variables, and compatible macro definitions into a new empty struct type, and then returns that type. + \\ + \\`expression` is interpreted at compile time. The builtin functions `@cInclude`, `@cDefine`, and `@cUndef` work within this expression, appending to a temporary buffer which is then parsed as C code. + \\ + \\Usually you should only have one `@cImport` in your entire application, because it saves the compiler from invoking clang multiple times, and prevents inline functions from being duplicated. + \\ + \\Reasons for having multiple `@cImport` expressions would be: + \\ + \\ - To avoid a symbol collision, for example if foo.h and bar.h both + \\`#define CONNECTION_COUNT` - To analyze the C code with different preprocessor defines + , + .arguments = &.{ + "expression", + }, + }, + .{ + .name = "@cInclude", + .signature = "@cInclude(comptime path: []u8)", + .snippet = "@cInclude(${1:comptime path: []u8})", + .documentation = + \\This function can only occur inside `@cImport`. + \\ + \\This appends + \\`#include <$path>\n`to the `c_import` temporary buffer. + , + .arguments = &.{ + "comptime path: []u8", + }, + }, + .{ + .name = "@clz", + .signature = "@clz(operand: anytype)", + .snippet = "@clz(${1:operand: anytype})", + .documentation = + \\`@TypeOf(operand)` must be an integer type or an integer vector type. + \\ + \\`operand` may be an [integer](https://ziglang.org/documentation/0.10.1/#Integers) or [vector](https://ziglang.org/documentation/0.10.1/#Vectors). + \\ + \\This function counts the number of most-significant (leading in a big-Endian sense) zeroes in an integer. + \\ + \\If `operand` is a [comptime](https://ziglang.org/documentation/0.10.1/#comptime)-known integer, the return type is `comptime_int`. Otherwise, the return type is an unsigned integer or vector of unsigned integers with the minimum number of bits that can represent the bit count of the integer type. + \\ + \\If `operand` is zero, `@clz` returns the bit width of integer type `T`. + , + .arguments = &.{ + "operand: anytype", + }, + }, + .{ + .name = "@cmpxchgStrong", + .signature = "@cmpxchgStrong(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T", + .snippet = "@cmpxchgStrong(${1:comptime T: type}, ${2:ptr: *T}, ${3:expected_value: T}, ${4:new_value: T}, ${5:success_order: AtomicOrder}, ${6:fail_order: AtomicOrder})", + .documentation = + \\This function performs a strong atomic compare exchange operation. It's the equivalent of this code, except atomic: + \\ + \\```zig + \\fn cmpxchgStrongButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_value: T) ?T { + \\ const old_value = ptr.*; + \\ if (old_value == expected_value) { + \\ ptr.* = new_value; + \\ return null; + \\ } else { + \\ return old_value; + \\ } + \\} + \\``` + \\If you are using cmpxchg in a loop, [@cmpxchgWeak](https://ziglang.org/documentation/0.10.1/#cmpxchgWeak) is the better choice, because it can be implemented more efficiently in machine instructions. + \\ + \\`T` must be a pointer, a `bool`, a float, an integer or an enum. + \\ + \\`@typeInfo(@TypeOf(ptr)).Pointer.alignment` must be `>= @sizeOf(T).` + , + .arguments = &.{ + "comptime T: type", + "ptr: *T", + "expected_value: T", + "new_value: T", + "success_order: AtomicOrder", + "fail_order: AtomicOrder", + }, + }, + .{ + .name = "@cmpxchgWeak", + .signature = "@cmpxchgWeak(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T", + .snippet = "@cmpxchgWeak(${1:comptime T: type}, ${2:ptr: *T}, ${3:expected_value: T}, ${4:new_value: T}, ${5:success_order: AtomicOrder}, ${6:fail_order: AtomicOrder})", + .documentation = + \\This function performs a weak atomic compare exchange operation. It's the equivalent of this code, except atomic: + \\ + \\```zig + \\fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_value: T) ?T { + \\ const old_value = ptr.*; + \\ if (old_value == expected_value and usuallyTrueButSometimesFalse()) { + \\ ptr.* = new_value; + \\ return null; + \\ } else { + \\ return old_value; + \\ } + \\} + \\``` + \\If you are using cmpxchg in a loop, the sporadic failure will be no problem, and `cmpxchgWeak` is the better choice, because it can be implemented more efficiently in machine instructions. However if you need a stronger guarantee, use [@cmpxchgStrong](https://ziglang.org/documentation/0.10.1/#cmpxchgStrong). + \\ + \\`T` must be a pointer, a `bool`, a float, an integer or an enum. + \\ + \\`@typeInfo(@TypeOf(ptr)).Pointer.alignment` must be `>= @sizeOf(T).` + , + .arguments = &.{ + "comptime T: type", + "ptr: *T", + "expected_value: T", + "new_value: T", + "success_order: AtomicOrder", + "fail_order: AtomicOrder", + }, + }, + .{ + .name = "@compileError", + .signature = "@compileError(comptime msg: []u8)", + .snippet = "@compileError(${1:comptime msg: []u8})", + .documentation = + \\This function, when semantically analyzed, causes a compile error with the message `msg`. + \\ + \\There are several ways that code avoids being semantically checked, such as using `if` or `switch` with compile time constants, and `comptime` functions. + , + .arguments = &.{ + "comptime msg: []u8", + }, + }, + .{ + .name = "@compileLog", + .signature = "@compileLog(args: ...)", + .snippet = "@compileLog(${1:args: ...})", + .documentation = + \\This function prints the arguments passed to it at compile-time. + \\ + \\To prevent accidentally leaving compile log statements in a codebase, a compilation error is added to the build, pointing to the compile log statement. This error prevents code from being generated, but does not otherwise interfere with analysis. + \\ + \\This function can be used to do "printf debugging" on compile-time executing code. + \\ + \\```zig + \\const print = @import("std").debug.print; + \\const num1 = blk: { + \\ var val1: i32 = 99; + \\ @compileLog("comptime val1 = ", val1); + \\ val1 = val1 + 1; + \\ break :blk val1; + \\}; + \\test "main" { + \\ @compileLog("comptime in main"); + \\ print("Runtime in main, num1 = {}.\n", .{num1}); + \\} + \\``` + \\If all `@compileLog` calls are removed or not encountered by analysis, the program compiles successfully and the generated executable prints: + \\ + \\```zig + \\const print = @import("std").debug.print; + \\const num1 = blk: { + \\ var val1: i32 = 99; + \\ val1 = val1 + 1; + \\ break :blk val1; + \\}; + \\test "main" { + \\ print("Runtime in main, num1 = {}.\n", .{num1}); + \\} + \\``` + , + .arguments = &.{ + "args: ...", + }, + }, + .{ + .name = "@ctz", + .signature = "@ctz(operand: anytype)", + .snippet = "@ctz(${1:operand: anytype})", + .documentation = + \\`@TypeOf(operand)` must be an integer type or an integer vector type. + \\ + \\`operand` may be an [integer](https://ziglang.org/documentation/0.10.1/#Integers) or [vector](https://ziglang.org/documentation/0.10.1/#Vectors). + \\ + \\This function counts the number of least-significant (trailing in a big-Endian sense) zeroes in an integer. + \\ + \\If `operand` is a [comptime](https://ziglang.org/documentation/0.10.1/#comptime)-known integer, the return type is `comptime_int`. Otherwise, the return type is an unsigned integer or vector of unsigned integers with the minimum number of bits that can represent the bit count of the integer type. + \\ + \\If `operand` is zero, `@ctz` returns the bit width of integer type `T`. + , + .arguments = &.{ + "operand: anytype", + }, + }, + .{ + .name = "@cUndef", + .signature = "@cUndef(comptime name: []u8)", + .snippet = "@cUndef(${1:comptime name: []u8})", + .documentation = + \\This function can only occur inside `@cImport`. + \\ + \\This appends + \\`#undef $name`to the `@cImport` temporary buffer. + , + .arguments = &.{ + "comptime name: []u8", + }, + }, + .{ + .name = "@divExact", + .signature = "@divExact(numerator: T, denominator: T) T", + .snippet = "@divExact(${1:numerator: T}, ${2:denominator: T})", + .documentation = + \\Exact division. Caller guarantees `denominator != 0` and `@divTrunc(numerator, denominator) * denominator == numerator`. + \\ + \\ - `@divExact(6, 3) == 2` + \\ - `@divExact(a, b) * b == a` + \\For a function that returns a possible error code, use `@import("std").math.divExact`. + , + .arguments = &.{ + "numerator: T", + "denominator: T", + }, + }, + .{ + .name = "@divFloor", + .signature = "@divFloor(numerator: T, denominator: T) T", + .snippet = "@divFloor(${1:numerator: T}, ${2:denominator: T})", + .documentation = + \\Floored division. Rounds toward negative infinity. For unsigned integers it is the same as `numerator / denominator`. Caller guarantees `denominator != 0` and `!(@typeInfo(T) == .Int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1)`. + \\ + \\ - `@divFloor(-5, 3) == -2` + \\ - `(@divFloor(a, b) * b) + @mod(a, b) == a` + \\For a function that returns a possible error code, use `@import("std").math.divFloor`. + , + .arguments = &.{ + "numerator: T", + "denominator: T", + }, + }, + .{ + .name = "@divTrunc", + .signature = "@divTrunc(numerator: T, denominator: T) T", + .snippet = "@divTrunc(${1:numerator: T}, ${2:denominator: T})", + .documentation = + \\Truncated division. Rounds toward zero. For unsigned integers it is the same as `numerator / denominator`. Caller guarantees `denominator != 0` and `!(@typeInfo(T) == .Int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1)`. + \\ + \\ - `@divTrunc(-5, 3) == -1` + \\ - `(@divTrunc(a, b) * b) + @rem(a, b) == a` + \\For a function that returns a possible error code, use `@import("std").math.divTrunc`. + , + .arguments = &.{ + "numerator: T", + "denominator: T", + }, + }, + .{ + .name = "@embedFile", + .signature = "@embedFile(comptime path: []const u8) *const [N:0]u8", + .snippet = "@embedFile(${1:comptime path: []const u8})", + .documentation = + \\This function returns a compile time constant pointer to null-terminated, fixed-size array with length equal to the byte count of the file given by `path`. The contents of the array are the contents of the file. This is equivalent to a [string literal](https://ziglang.org/documentation/0.10.1/#String-Literals-and-Unicode-Code-Point-Literals) with the file contents. + \\ + \\`path` is absolute or relative to the current file, just like `@import`. + , + .arguments = &.{ + "comptime path: []const u8", + }, + }, + .{ + .name = "@enumToInt", + .signature = "@enumToInt(enum_or_tagged_union: anytype) anytype", + .snippet = "@enumToInt(${1:enum_or_tagged_union: anytype})", + .documentation = + \\Converts an enumeration value into its integer tag type. When a tagged union is passed, the tag value is used as the enumeration value. + \\ + \\If there is only one possible enum value, the result is a `comptime_int` known at [comptime](https://ziglang.org/documentation/0.10.1/#comptime). + , + .arguments = &.{ + "enum_or_tagged_union: anytype", + }, + }, + .{ + .name = "@errorName", + .signature = "@errorName(err: anyerror) [:0]const u8", + .snippet = "@errorName(${1:err: anyerror})", + .documentation = + \\This function returns the string representation of an error. The string representation of `error.OutOfMem` is `"OutOfMem"`. + \\ + \\If there are no calls to `@errorName` in an entire application, or all calls have a compile-time known value for `err`, then no error name table will be generated. + , + .arguments = &.{ + "err: anyerror", + }, + }, + .{ + .name = "@errorReturnTrace", + .signature = "@errorReturnTrace() ?*builtin.StackTrace", + .snippet = "@errorReturnTrace()", + .documentation = + \\If the binary is built with error return tracing, and this function is invoked in a function that calls a function with an error or error union return type, returns a stack trace object. Otherwise returns [null](https://ziglang.org/documentation/0.10.1/#null). + , + .arguments = &.{}, + }, + .{ + .name = "@errorToInt", + .signature = "@errorToInt(err: anytype) std.meta.Int(.unsigned, @sizeOf(anyerror) * 8)", + .snippet = "@errorToInt(${1:err: anytype})", + .documentation = + \\Supports the following types: + \\ + \\ - [The Global Error Set](https://ziglang.org/documentation/0.10.1/#The-Global-Error-Set) + \\ - [Error Set Type](https://ziglang.org/documentation/0.10.1/#Error-Set-Type) + \\ - [Error Union Type](https://ziglang.org/documentation/0.10.1/#Error-Union-Type) + \\Converts an error to the integer representation of an error. + \\ + \\It is generally recommended to avoid this cast, as the integer representation of an error is not stable across source code changes. + , + .arguments = &.{ + "err: anytype", + }, + }, + .{ + .name = "@errSetCast", + .signature = "@errSetCast(comptime T: DestType, value: anytype) DestType", + .snippet = "@errSetCast(${1:comptime T: DestType}, ${2:value: anytype})", + .documentation = + \\Converts an error value from one error set to another error set. Attempting to convert an error which is not in the destination error set results in safety-protected [Undefined Behavior](https://ziglang.org/documentation/0.10.1/#Undefined-Behavior). + , + .arguments = &.{ + "comptime T: DestType", + "value: anytype", + }, + }, + .{ + .name = "@export", + .signature = "@export(declaration, comptime options: std.builtin.ExportOptions) void", + .snippet = "@export(${1:declaration}, ${2:comptime options: std.builtin.ExportOptions})", + .documentation = + \\Creates a symbol in the output object file. + \\ + \\`declaration`must be one of two things: + \\ + \\ - An identifier (`x`) identifying a [function](https://ziglang.org/documentation/0.10.1/#Functions) or a [variable](https://ziglang.org/documentation/0.10.1/#Container-Level-Variables). + \\ - Field access (`x.y`) looking up a [function](https://ziglang.org/documentation/0.10.1/#Functions) or a [variable](https://ziglang.org/documentation/0.10.1/#Container-Level-Variables). + \\This builtin can be called from a [comptime](https://ziglang.org/documentation/0.10.1/#comptime) block to conditionally export symbols. When + \\`declaration`is a function with the C calling convention and `options.linkage` is `Strong`, this is equivalent to the `export` keyword used on a function: + \\ + \\```zig + \\comptime { + \\ @export(internalName, .{ .name = "foo", .linkage = .Strong }); + \\} + \\fn internalName() callconv(.C) void {} + \\``` + \\This is equivalent to: + \\ + \\`export fn foo() void {}` + \\Note that even when using `export`, the `@"foo"` syntax for [identifiers](https://ziglang.org/documentation/0.10.1/#Identifiers) can be used to choose any string for the symbol name: + \\ + \\`export fn @"A function name that is a complete sentence."() void {}` + \\When looking at the resulting object, you can see the symbol is used verbatim: + \\ + \\`00000000000001f0 T A function name that is a complete sentence.` + , + .arguments = &.{ + "declaration", + "comptime options: std.builtin.ExportOptions", + }, + }, + .{ + .name = "@extern", + .signature = "@extern(T: type, comptime options: std.builtin.ExternOptions) *T", + .snippet = "@extern(${1:T: type}, ${2:comptime options: std.builtin.ExternOptions})", + .documentation = + \\Creates a reference to an external symbol in the output object file. + , + .arguments = &.{ + "T: type", + "comptime options: std.builtin.ExternOptions", + }, + }, + .{ + .name = "@fence", + .signature = "@fence(order: AtomicOrder)", + .snippet = "@fence(${1:order: AtomicOrder})", + .documentation = + \\The `fence` function is used to introduce happens-before edges between operations. + \\ + \\`AtomicOrder` can be found with `@import("std").builtin.AtomicOrder`. + , + .arguments = &.{ + "order: AtomicOrder", + }, + }, + .{ + .name = "@field", + .signature = "@field(lhs: anytype, comptime field_name: []const u8) (field)", + .snippet = "@field(${1:lhs: anytype}, ${2:comptime field_name: []const u8})", + .documentation = + \\Performs field access by a compile-time string. Works on both fields and declarations. + \\ + \\```zig + \\const std = @import("std"); + \\const Point = struct { + \\ x: u32, + \\ y: u32, + \\ pub var z: u32 = 1; + \\}; + \\test "field access by string" { + \\ const expect = std.testing.expect; + \\ var p = Point{ .x = 0, .y = 0 }; + \\ @field(p, "x") = 4; + \\ @field(p, "y") = @field(p, "x") + 1; + \\ try expect(@field(p, "x") == 4); + \\ try expect(@field(p, "y") == 5); + \\} + \\test "decl access by string" { + \\ const expect = std.testing.expect; + \\ try expect(@field(Point, "z") == 1); + \\ @field(Point, "z") = 2; + \\ try expect(@field(Point, "z") == 2); + \\} + \\``` + , + .arguments = &.{ + "lhs: anytype", + "comptime field_name: []const u8", + }, + }, + .{ + .name = "@fieldParentPtr", + .signature = "@fieldParentPtr(comptime ParentType: type, comptime field_name: []const u8, field_ptr: *T) *ParentType", + .snippet = "@fieldParentPtr(${1:comptime ParentType: type}, ${2:comptime field_name: []const u8}, ${3:field_ptr: *T})", + .documentation = + \\Given a pointer to a field, returns the base pointer of a struct. + , + .arguments = &.{ + "comptime ParentType: type", + "comptime field_name: []const u8", + "field_ptr: *T", + }, + }, + .{ + .name = "@floatCast", + .signature = "@floatCast(comptime DestType: type, value: anytype) DestType", + .snippet = "@floatCast(${1:comptime DestType: type}, ${2:value: anytype})", + .documentation = + \\Convert from one float type to another. This cast is safe, but may cause the numeric value to lose precision. + , + .arguments = &.{ + "comptime DestType: type", + "value: anytype", + }, + }, + .{ + .name = "@floatToInt", + .signature = "@floatToInt(comptime DestType: type, float: anytype) DestType", + .snippet = "@floatToInt(${1:comptime DestType: type}, ${2:float: anytype})", + .documentation = + \\Converts the integer part of a floating point number to the destination type. + \\ + \\If the integer part of the floating point number cannot fit in the destination type, it invokes safety-checked [Undefined Behavior](https://ziglang.org/documentation/0.10.1/#Undefined-Behavior). + , + .arguments = &.{ + "comptime DestType: type", + "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/0.10.1/#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/0.10.1/#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/0.10.1/#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/0.10.1/#Async-and-Await) which allows one to, for example, heap-allocate an async function frame: + \\ + \\```zig + \\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", + .snippet = "@frameAddress()", + .documentation = + \\This function returns the base pointer of the current stack frame. + \\ + \\The implications of this are target-specific and not consistent across all platforms. The frame address may not be available in release mode due to aggressive optimizations. + \\ + \\This function is only valid within function scope. + , + .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/0.10.1/#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 [container](https://ziglang.org/documentation/0.10.1/#Containers) has a declaration matching `name`. + \\ + \\```zig + \\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", + "comptime name: []const u8", + }, + }, + .{ + .name = "@hasField", + .signature = "@hasField(comptime Container: type, comptime name: []const u8) bool", + .snippet = "@hasField(${1:comptime Container: type}, ${2:comptime name: []const u8})", + .documentation = + \\Returns whether the field name of a struct, union, or enum exists. + \\ + \\The result is a compile time constant. + \\ + \\It does not include functions, variables, or constants. + , + .arguments = &.{ + "comptime Container: type", + "comptime name: []const u8", + }, + }, + .{ + .name = "@import", + .signature = "@import(comptime path: []u8) type", + .snippet = "@import(${1:comptime path: []u8})", + .documentation = + \\This function finds a zig file corresponding to `path` and adds it to the build, if it is not already added. + \\ + \\Zig source files are implicitly structs, with a name equal to the file's basename with the extension truncated. `@import` returns the struct type corresponding to the file. + \\ + \\Declarations which have the `pub` keyword may be referenced from a different source file than the one they are declared in. + \\ + \\`path` can be a relative path or it can be the name of a package. If it is a relative path, it is relative to the file that contains the `@import` function call. + \\ + \\The following packages are always available: + \\ + \\ - `@import("std")` - Zig Standard Library + \\ - `@import("builtin")` - Target-specific information. The command + \\`zig build-exe --show-builtin`outputs the source to stdout for reference. + \\ - `@import("root")` - Points to the root source file. This is usually + \\`src/main.zig`but it depends on what file is chosen to be built. + , + .arguments = &.{ + "comptime path: []u8", + }, + }, + .{ + .name = "@intCast", + .signature = "@intCast(comptime DestType: type, int: anytype) DestType", + .snippet = "@intCast(${1:comptime DestType: type}, ${2:int: anytype})", + .documentation = + \\Converts an integer to another integer while keeping the same numerical value. Attempting to convert a number which is out of range of the destination type results in safety-protected [Undefined Behavior](https://ziglang.org/documentation/0.10.1/#Undefined-Behavior). + \\ + \\```zig + \\test "integer cast panic" { + \\ var a: u16 = 0xabcd; + \\ var b: u8 = @intCast(u8, a); + \\ _ = b; + \\} + \\``` + \\To truncate the significant bits of a number out of range of the destination type, use [@truncate](https://ziglang.org/documentation/0.10.1/#truncate). + \\ + \\If `T` is `comptime_int`, then this is semantically equivalent to [Type Coercion](https://ziglang.org/documentation/0.10.1/#Type-Coercion). + , + .arguments = &.{ + "comptime DestType: type", + "int: anytype", + }, + }, + .{ + .name = "@intToEnum", + .signature = "@intToEnum(comptime DestType: type, integer: anytype) DestType", + .snippet = "@intToEnum(${1:comptime DestType: type}, ${2:integer: anytype})", + .documentation = + \\Converts an integer into an [enum](https://ziglang.org/documentation/0.10.1/#enum) value. + \\ + \\Attempting to convert an integer which represents no value in the chosen enum type invokes safety-checked [Undefined Behavior](https://ziglang.org/documentation/0.10.1/#Undefined-Behavior). + , + .arguments = &.{ + "comptime DestType: type", + "integer: anytype", + }, + }, + .{ + .name = "@intToError", + .signature = "@intToError(value: std.meta.Int(.unsigned, @sizeOf(anyerror) * 8)) anyerror", + .snippet = "@intToError(${1:value: std.meta.Int(.unsigned, @sizeOf(anyerror) * 8})", + .documentation = + \\Converts from the integer representation of an error into [The Global Error Set](https://ziglang.org/documentation/0.10.1/#The-Global-Error-Set) type. + \\ + \\It is generally recommended to avoid this cast, as the integer representation of an error is not stable across source code changes. + \\ + \\Attempting to convert an integer that does not correspond to any error results in safety-protected [Undefined Behavior](https://ziglang.org/documentation/0.10.1/#Undefined-Behavior). + , + .arguments = &.{ + "value: std.meta.Int(.unsigned, @sizeOf(anyerror) * 8", + }, + }, + .{ + .name = "@intToFloat", + .signature = "@intToFloat(comptime DestType: type, int: anytype) DestType", + .snippet = "@intToFloat(${1:comptime DestType: type}, ${2:int: anytype})", + .documentation = + \\Converts an integer to the closest floating point representation. To convert the other way, use [@floatToInt](https://ziglang.org/documentation/0.10.1/#floatToInt). This cast is always safe. + , + .arguments = &.{ + "comptime DestType: type", + "int: anytype", + }, + }, + .{ + .name = "@intToPtr", + .signature = "@intToPtr(comptime DestType: type, address: usize) DestType", + .snippet = "@intToPtr(${1:comptime DestType: type}, ${2:address: usize})", + .documentation = + \\Converts an integer to a [pointer](https://ziglang.org/documentation/0.10.1/#Pointers). To convert the other way, use [@ptrToInt](https://ziglang.org/documentation/0.10.1/#ptrToInt). Casting an address of 0 to a destination type which in not [optional](https://ziglang.org/documentation/0.10.1/#Optional-Pointers) and does not have the `allowzero` attribute will result in a [Pointer Cast Invalid Null](https://ziglang.org/documentation/0.10.1/#Pointer-Cast-Invalid-Null) panic when runtime safety checks are enabled. + \\ + \\If the destination pointer type does not allow address zero and `address` is zero, this invokes safety-checked [Undefined Behavior](https://ziglang.org/documentation/0.10.1/#Undefined-Behavior). + , + .arguments = &.{ + "comptime DestType: type", + "address: usize", + }, + }, + .{ + .name = "@max", + .signature = "@max(a: T, b: T) T", + .snippet = "@max(${1:a: T}, ${2:b: T})", + .documentation = + \\Returns the maximum value of `a` and `b`. This builtin accepts integers, floats, and vectors of either. In the latter case, the operation is performed element wise. + \\ + \\NaNs are handled as follows: if one of the operands of a (pairwise) operation is NaN, the other operand is returned. If both operands are NaN, NaN is returned. + , + .arguments = &.{ + "a: T", + "b: T", + }, + }, + .{ + .name = "@memcpy", + .signature = "@memcpy(noalias dest: [*]u8, noalias source: [*]const u8, byte_count: usize)", + .snippet = "@memcpy(${1:noalias dest: [*]u8}, ${2:noalias source: [*]const u8}, ${3:byte_count: usize})", + .documentation = + \\This function copies bytes from one region of memory to another. `dest` and `source` are both pointers and must not overlap. + \\ + \\This function is a low level intrinsic with no safety mechanisms. Most code should not use this function, instead using something like this: + \\ + \\`for (source[0..byte_count]) |b, i| dest[i] = b;` + \\The optimizer is intelligent enough to turn the above snippet into a memcpy. + \\ + \\There is also a standard library function for this: + \\ + \\```zig + \\const mem = @import("std").mem; + \\mem.copy(u8, dest[0..byte_count], source[0..byte_count]); + \\``` + , + .arguments = &.{ + "noalias dest: [*]u8", + "noalias source: [*]const u8", + "byte_count: usize", + }, + }, + .{ + .name = "@memset", + .signature = "@memset(dest: [*]u8, c: u8, byte_count: usize)", + .snippet = "@memset(${1:dest: [*]u8}, ${2:c: u8}, ${3:byte_count: usize})", + .documentation = + \\This function sets a region of memory to `c`. `dest` is a pointer. + \\ + \\This function is a low level intrinsic with no safety mechanisms. Most code should not use this function, instead using something like this: + \\ + \\`for (dest[0..byte_count]) |*b| b.* = c;` + \\The optimizer is intelligent enough to turn the above snippet into a memset. + \\ + \\There is also a standard library function for this: + \\ + \\```zig + \\const mem = @import("std").mem; + \\mem.set(u8, dest, c); + \\``` + , + .arguments = &.{ + "dest: [*]u8", + "c: u8", + "byte_count: usize", + }, + }, + .{ + .name = "@min", + .signature = "@min(a: T, b: T) T", + .snippet = "@min(${1:a: T}, ${2:b: T})", + .documentation = + \\Returns the minimum value of `a` and `b`. This builtin accepts integers, floats, and vectors of either. In the latter case, the operation is performed element wise. + \\ + \\NaNs are handled as follows: if one of the operands of a (pairwise) operation is NaN, the other operand is returned. If both operands are NaN, NaN is returned. + , + .arguments = &.{ + "a: T", + "b: T", + }, + }, + .{ + .name = "@wasmMemorySize", + .signature = "@wasmMemorySize(index: u32) u32", + .snippet = "@wasmMemorySize(${1:index: u32})", + .documentation = + \\This function returns the size of the Wasm memory identified by `index` as an unsigned value in units of Wasm pages. Note that each Wasm page is 64KB in size. + \\ + \\This function is a low level intrinsic with no safety mechanisms usually useful for allocator designers targeting Wasm. So unless you are writing a new allocator from scratch, you should use something like `@import("std").heap.WasmPageAllocator`. + , + .arguments = &.{ + "index: u32", + }, + }, + .{ + .name = "@wasmMemoryGrow", + .signature = "@wasmMemoryGrow(index: u32, delta: u32) i32", + .snippet = "@wasmMemoryGrow(${1:index: u32}, ${2:delta: u32})", + .documentation = + \\This function increases the size of the Wasm memory identified by `index` by `delta` in units of unsigned number of Wasm pages. Note that each Wasm page is 64KB in size. On success, returns previous memory size; on failure, if the allocation fails, returns -1. + \\ + \\This function is a low level intrinsic with no safety mechanisms usually useful for allocator designers targeting Wasm. So unless you are writing a new allocator from scratch, you should use something like `@import("std").heap.WasmPageAllocator`. + \\ + \\```zig + \\const std = @import("std"); + \\const native_arch = @import("builtin").target.cpu.arch; + \\const expect = std.testing.expect; + \\test "@wasmMemoryGrow" { + \\ if (native_arch != .wasm32) return error.SkipZigTest; + \\ var prev = @wasmMemorySize(0); + \\ try expect(prev == @wasmMemoryGrow(0, 1)); + \\ try expect(prev + 1 == @wasmMemorySize(0)); + \\} + \\``` + , + .arguments = &.{ + "index: u32", + "delta: u32", + }, + }, + .{ + .name = "@mod", + .signature = "@mod(numerator: T, denominator: T) T", + .snippet = "@mod(${1:numerator: T}, ${2:denominator: T})", + .documentation = + \\Modulus division. For unsigned integers this is the same as `numerator % denominator`. Caller guarantees `denominator > 0`, otherwise the operation will result in a [Remainder Division by Zero](https://ziglang.org/documentation/0.10.1/#Remainder-Division-by-Zero) when runtime safety checks are enabled. + \\ + \\ - `@mod(-5, 3) == 1` + \\ - `(@divFloor(a, b) * b) + @mod(a, b) == a` + \\For a function that returns an error code, see `@import("std").math.mod`. + , + .arguments = &.{ + "numerator: T", + "denominator: T", + }, + }, + .{ + .name = "@mulWithOverflow", + .signature = "@mulWithOverflow(comptime T: type, a: T, b: T, result: *T) bool", + .snippet = "@mulWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:b: T}, ${4:result: *T})", + .documentation = + \\Performs `result.* = a * b`. If overflow or underflow occurs, stores the overflowed bits in `result` and returns `true`. If no overflow or underflow occurs, returns `false`. + , + .arguments = &.{ + "comptime T: type", + "a: T", + "b: T", + "result: *T", + }, + }, + .{ + .name = "@panic", + .signature = "@panic(message: []const u8) noreturn", + .snippet = "@panic(${1:message: []const u8})", + .documentation = + \\Invokes the panic handler function. By default the panic handler function calls the public `panic` function exposed in the root source file, or if there is not one specified, the `std.builtin.default_panic` function from `std/builtin.zig`. + \\ + \\Generally it is better to use `@import("std").debug.panic`. However, `@panic` can be useful for 2 scenarios: + \\ + \\ - From library code, calling the programmer's panic function if they exposed one in the root source file. + \\ - When mixing C and Zig code, calling the canonical panic implementation across multiple .o files. + , + .arguments = &.{ + "message: []const u8", + }, + }, + .{ + .name = "@popCount", + .signature = "@popCount(operand: anytype)", + .snippet = "@popCount(${1:operand: anytype})", + .documentation = + \\`@TypeOf(operand)` must be an integer type. + \\ + \\`operand` may be an [integer](https://ziglang.org/documentation/0.10.1/#Integers) or [vector](https://ziglang.org/documentation/0.10.1/#Vectors). + \\ + \\Counts the number of bits set in an integer. + \\ + \\If `operand` is a [comptime](https://ziglang.org/documentation/0.10.1/#comptime)-known integer, the return type is `comptime_int`. Otherwise, the return type is an unsigned integer or vector of unsigned integers with the minimum number of bits that can represent the bit count of the integer type. + , + .arguments = &.{ + "operand: anytype", + }, + }, + .{ + .name = "@prefetch", + .signature = "@prefetch(ptr: anytype, comptime options: std.builtin.PrefetchOptions)", + .snippet = "@prefetch(${1:ptr: anytype}, ${2:comptime options: std.builtin.PrefetchOptions})", + .documentation = + \\This builtin tells the compiler to emit a prefetch instruction if supported by the target CPU. If the target CPU does not support the requested prefetch instruction, this builtin is a no-op. This function has no effect on the behavior of the program, only on the performance characteristics. + \\ + \\The `ptr` argument may be any pointer type and determines the memory address to prefetch. This function does not dereference the pointer, it is perfectly legal to pass a pointer to invalid memory to this function and no illegal behavior will result. + \\ + \\The `options` argument is the following struct: + \\ + \\```zig + \\/// This data structure is used by the Zig language code generation and + \\/// therefore must be kept in sync with the compiler implementation. + \\pub const PrefetchOptions = struct { + \\ /// Whether the prefetch should prepare for a read or a write. + \\ rw: Rw = .read, + \\ /// 0 means no temporal locality. That is, the data can be immediately + \\ /// dropped from the cache after it is accessed. + \\ /// + \\ /// 3 means high temporal locality. That is, the data should be kept in + \\ /// the cache as it is likely to be accessed again soon. + \\ locality: u2 = 3, + \\ /// The cache that the prefetch should be preformed on. + \\ cache: Cache = .data, + \\ pub const Rw = enum { + \\ read, + \\ write, + \\ }; + \\ pub const Cache = enum { + \\ instruction, + \\ data, + \\ }; + \\}; + \\``` + , + .arguments = &.{ + "ptr: anytype", + "comptime options: std.builtin.PrefetchOptions", + }, + }, + .{ + .name = "@ptrCast", + .signature = "@ptrCast(comptime DestType: type, value: anytype) DestType", + .snippet = "@ptrCast(${1:comptime DestType: type}, ${2:value: anytype})", + .documentation = + \\Converts a pointer of one type to a pointer of another type. + \\ + \\[Optional Pointers](https://ziglang.org/documentation/0.10.1/#Optional-Pointers) are allowed. Casting an optional pointer which is [null](https://ziglang.org/documentation/0.10.1/#null) to a non-optional pointer invokes safety-checked [Undefined Behavior](https://ziglang.org/documentation/0.10.1/#Undefined-Behavior). + , + .arguments = &.{ + "comptime DestType: type", + "value: anytype", + }, + }, + .{ + .name = "@ptrToInt", + .signature = "@ptrToInt(value: anytype) usize", + .snippet = "@ptrToInt(${1:value: anytype})", + .documentation = + \\Converts `value` to a `usize` which is the address of the pointer. `value` can be `*T` or `?*T`. + \\ + \\To convert the other way, use [@intToPtr](https://ziglang.org/documentation/0.10.1/#intToPtr) + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@rem", + .signature = "@rem(numerator: T, denominator: T) T", + .snippet = "@rem(${1:numerator: T}, ${2:denominator: T})", + .documentation = + \\Remainder division. For unsigned integers this is the same as `numerator % denominator`. Caller guarantees `denominator > 0`, otherwise the operation will result in a [Remainder Division by Zero](https://ziglang.org/documentation/0.10.1/#Remainder-Division-by-Zero) when runtime safety checks are enabled. + \\ + \\ - `@rem(-5, 3) == -2` + \\ - `(@divTrunc(a, b) * b) + @rem(a, b) == a` + \\For a function that returns an error code, see `@import("std").math.rem`. + , + .arguments = &.{ + "numerator: T", + "denominator: T", + }, + }, + .{ + .name = "@returnAddress", + .signature = "@returnAddress() usize", + .snippet = "@returnAddress()", + .documentation = + \\This function returns the address of the next machine code instruction that will be executed when the current function returns. + \\ + \\The implications of this are target-specific and not consistent across all platforms. + \\ + \\This function is only valid within function scope. If the function gets inlined into a calling function, the returned address will apply to the calling function. + , + .arguments = &.{}, + }, + .{ + .name = "@select", + .signature = "@select(comptime T: type, pred: @Vector(len, bool), a: @Vector(len, T), b: @Vector(len, T)) @Vector(len, T)", + .snippet = "@select(${1:comptime T: type}, ${2:pred: @Vector(len, bool)}, ${3:a: @Vector(len, T)}, ${4:b: @Vector(len, T)})", + .documentation = + \\Selects values element-wise from `a` or `b` based on `pred`. If `pred[i]` is `true`, the corresponding element in the result will be `a[i]` and otherwise `b[i]`. + , + .arguments = &.{ + "comptime T: type", + "pred: @Vector(len, bool)", + "a: @Vector(len, T)", + "b: @Vector(len, T)", + }, + }, + .{ + .name = "@setAlignStack", + .signature = "@setAlignStack(comptime alignment: u29)", + .snippet = "@setAlignStack(${1:comptime alignment: u29})", + .documentation = + \\Ensures that a function will have a stack alignment of at least `alignment` bytes. + , + .arguments = &.{ + "comptime alignment: u29", + }, + }, + .{ + .name = "@setCold", + .signature = "@setCold(comptime is_cold: bool)", + .snippet = "@setCold(${1:comptime is_cold: bool})", + .documentation = + \\Tells the optimizer that a function is rarely called. + , + .arguments = &.{ + "comptime is_cold: bool", + }, + }, + .{ + .name = "@setEvalBranchQuota", + .signature = "@setEvalBranchQuota(comptime new_quota: u32)", + .snippet = "@setEvalBranchQuota(${1:comptime new_quota: u32})", + .documentation = + \\Changes the maximum number of backwards branches that compile-time code execution can use before giving up and making a compile error. + \\ + \\If the `new_quota` is smaller than the default quota (`1000`) or a previously explicitly set quota, it is ignored. + \\ + \\Example: + \\ + \\1001) : (i += 1) {} + \\ } + \\} + \\``` + \\Now we use `@setEvalBranchQuota`: + \\ + \\1001) : (i += 1) {} + \\ } + \\} + \\``` + , + .arguments = &.{ + "comptime new_quota: u32", + }, + }, + .{ + .name = "@setFloatMode", + .signature = "@setFloatMode(comptime mode: @import(\"std\").builtin.FloatMode)", + .snippet = "@setFloatMode(${1:comptime mode: @import(\"std\").builtin.FloatMode})", + .documentation = + \\Sets the floating point mode of the current scope. Possible values are: + \\ + \\```zig + \\pub const FloatMode = enum { + \\ Strict, + \\ Optimized, + \\}; + \\``` + \\ - `Strict` (default) - Floating point operations follow strict IEEE compliance. + \\ - `Optimized` - Floating point operations may do all of the following: + \\ - Assume the arguments and result are not NaN. Optimizations are required to retain defined behavior over NaNs, but the value of the result is undefined. + \\ - Assume the arguments and result are not +/-Inf. Optimizations are required to retain defined behavior over +/-Inf, but the value of the result is undefined. + \\ - Treat the sign of a zero argument or result as insignificant. + \\ - Use the reciprocal of an argument rather than perform division. + \\ - Perform floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-add). + \\ - Perform algebraically equivalent transformations that may change results in floating point (e.g. reassociate). + \\This is equivalent to + \\`-ffast-math`in GCC. + \\The floating point mode is inherited by child scopes, and can be overridden in any scope. You can set the floating point mode in a struct or module scope by using a comptime block. + , + .arguments = &.{ + "comptime mode: @import(\"std\").builtin.FloatMode", + }, + }, + .{ + .name = "@setRuntimeSafety", + .signature = "@setRuntimeSafety(comptime safety_on: bool) void", + .snippet = "@setRuntimeSafety(${1:comptime safety_on: bool})", + .documentation = + \\Sets whether runtime safety checks are enabled for the scope that contains the function call. + \\ + \\```zig + \\test "@setRuntimeSafety" { + \\ // The builtin applies to the scope that it is called in. So here, integer overflow + \\ // will not be caught in ReleaseFast and ReleaseSmall modes: + \\ // var x: u8 = 255; + \\ // x += 1; // undefined behavior in ReleaseFast/ReleaseSmall modes. + \\ { + \\ // However this block has safety enabled, so safety checks happen here, + \\ // even in ReleaseFast and ReleaseSmall modes. + \\ @setRuntimeSafety(true); + \\ var x: u8 = 255; + \\ x += 1; + \\ { + \\ // The value can be overridden at any scope. So here integer overflow + \\ // would not be caught in any build mode. + \\ @setRuntimeSafety(false); + \\ // var x: u8 = 255; + \\ // x += 1; // undefined behavior in all build modes. + \\ } + \\ } + \\} + \\``` + \\Note: it is + \\[planned](https://github.com/ziglang/zig/issues/978)to replace `@setRuntimeSafety` with + \\`@optimizeFor` + , + .arguments = &.{ + "comptime safety_on: bool", + }, + }, + .{ + .name = "@shlExact", + .signature = "@shlExact(value: T, shift_amt: Log2T) T", + .snippet = "@shlExact(${1:value: T}, ${2:shift_amt: Log2T})", + .documentation = + \\`). For unsigned integers, the result is [undefined](https://ziglang.org/documentation/0.10.1/#undefined) if any 1 bits are shifted out. For signed integers, the result is [undefined](https://ziglang.org/documentation/0.10.1/#undefined) if any bits that disagree with the resultant sign bit are shifted out. + \\ + \\The type of `shift_amt` is an unsigned integer with `log2(@typeInfo(T).Int.bits)` bits. This is because `shift_amt >= @typeInfo(T).Int.bits` is undefined behavior. + , + .arguments = &.{ + "value: T", + "shift_amt: Log2T", + }, + }, + .{ + .name = "@shlWithOverflow", + .signature = "@shlWithOverflow(comptime T: type, a: T, shift_amt: Log2T, result: *T) bool", + .snippet = "@shlWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:shift_amt: Log2T}, ${4:result: *T})", + .documentation = + \\b`. If overflow or underflow occurs, stores the overflowed bits in `result` and returns `true`. If no overflow or underflow occurs, returns `false`. + \\ + \\The type of `shift_amt` is an unsigned integer with `log2(@typeInfo(T).Int.bits)` bits. This is because `shift_amt >= @typeInfo(T).Int.bits` is undefined behavior. + , + .arguments = &.{ + "comptime T: type", + "a: T", + "shift_amt: Log2T", + "result: *T", + }, + }, + .{ + .name = "@shrExact", + .signature = "@shrExact(value: T, shift_amt: Log2T) T", + .snippet = "@shrExact(${1:value: T}, ${2:shift_amt: Log2T})", + .documentation = + \\Performs the right shift operation (`>>`). Caller guarantees that the shift will not shift any 1 bits out. + \\ + \\The type of `shift_amt` is an unsigned integer with `log2(@typeInfo(T).Int.bits)` bits. This is because `shift_amt >= @typeInfo(T).Int.bits` is undefined behavior. + , + .arguments = &.{ + "value: T", + "shift_amt: Log2T", + }, + }, + .{ + .name = "@shuffle", + .signature = "@shuffle(comptime E: type, a: @Vector(a_len, E), b: @Vector(b_len, E), comptime mask: @Vector(mask_len, i32)) @Vector(mask_len, E)", + .snippet = "@shuffle(${1:comptime E: type}, ${2:a: @Vector(a_len, E)}, ${3:b: @Vector(b_len, E)}, ${4:comptime mask: @Vector(mask_len, i32)})", + .documentation = + \\Constructs a new [vector](https://ziglang.org/documentation/0.10.1/#Vectors) by selecting elements from `a` and `b` based on `mask`. + \\ + \\Each element in `mask` selects an element from either `a` or `b`. Positive numbers select from `a` starting at 0. Negative values select from `b`, starting at `-1` and going down. It is recommended to use the `~` operator for indexes from `b` so that both indexes can start from `0` (i.e. `~@as(i32, 0)` is `-1`). + \\ + \\For each element of `mask`, if it or the selected value from `a` or `b` is `undefined`, then the resulting element is `undefined`. + \\ + \\`a_len` and `b_len` may differ in length. Out-of-bounds element indexes in `mask` result in compile errors. + \\ + \\If `a` or `b` is `undefined`, it is equivalent to a vector of all `undefined` with the same length as the other vector. If both vectors are `undefined`, `@shuffle` returns a vector with all elements `undefined`. + \\ + \\`E` must be an [integer](https://ziglang.org/documentation/0.10.1/#Integers), [float](https://ziglang.org/documentation/0.10.1/#Floats), [pointer](https://ziglang.org/documentation/0.10.1/#Pointers), or `bool`. The mask may be any vector length, and its length determines the result length. + \\ + \\```zig + \\const std = @import("std"); + \\const expect = std.testing.expect; + \\test "vector @shuffle" { + \\ const a = @Vector(7, u8){ 'o', 'l', 'h', 'e', 'r', 'z', 'w' }; + \\ const b = @Vector(4, u8){ 'w', 'd', '!', 'x' }; + \\ // To shuffle within a single vector, pass undefined as the second argument. + \\ // Notice that we can re-order, duplicate, or omit elements of the input vector + \\ const mask1 = @Vector(5, i32){ 2, 3, 1, 1, 0 }; + \\ const res1: @Vector(5, u8) = @shuffle(u8, a, undefined, mask1); + \\ try expect(std.mem.eql(u8, &@as([5]u8, res1), "hello")); + \\ // Combining two vectors + \\ const mask2 = @Vector(6, i32){ -1, 0, 4, 1, -2, -3 }; + \\ const res2: @Vector(6, u8) = @shuffle(u8, a, b, mask2); + \\ try expect(std.mem.eql(u8, &@as([6]u8, res2), "world!")); + \\} + \\``` + , + .arguments = &.{ + "comptime E: type", + "a: @Vector(a_len, E)", + "b: @Vector(b_len, E)", + "comptime mask: @Vector(mask_len, i32)", + }, + }, + .{ + .name = "@sizeOf", + .signature = "@sizeOf(comptime T: type) comptime_int", + .snippet = "@sizeOf(${1:comptime T: type})", + .documentation = + \\This function returns the number of bytes it takes to store `T` in memory. The result is a target-specific compile time constant. + \\ + \\This size may contain padding bytes. If there were two consecutive T in memory, this would be the offset in bytes between element at index 0 and the element at index 1. For [integer](https://ziglang.org/documentation/0.10.1/#Integers), consider whether you want to use `@sizeOf(T)` or `@typeInfo(T).Int.bits`. + \\ + \\This function measures the size at runtime. For types that are disallowed at runtime, such as `comptime_int` and `type`, the result is `0`. + , + .arguments = &.{ + "comptime T: type", + }, + }, + .{ + .name = "@splat", + .signature = "@splat(comptime len: u32, scalar: anytype) @Vector(len, @TypeOf(scalar))", + .snippet = "@splat(${1:comptime len: u32}, ${2:scalar: anytype})", + .documentation = + \\Produces a vector of length `len` where each element is the value `scalar`: + \\ + \\```zig + \\const std = @import("std"); + \\const expect = std.testing.expect; + \\test "vector @splat" { + \\ const scalar: u32 = 5; + \\ const result = @splat(4, scalar); + \\ comptime try expect(@TypeOf(result) == @Vector(4, u32)); + \\ try expect(std.mem.eql(u32, &@as([4]u32, result), &[_]u32{ 5, 5, 5, 5 })); + \\} + \\``` + \\`scalar` must be an [integer](https://ziglang.org/documentation/0.10.1/#Integers), [bool](https://ziglang.org/documentation/0.10.1/#Primitive-Types), [float](https://ziglang.org/documentation/0.10.1/#Floats), or [pointer](https://ziglang.org/documentation/0.10.1/#Pointers). + , + .arguments = &.{ + "comptime len: u32", + "scalar: anytype", + }, + }, + .{ + .name = "@reduce", + .signature = "@reduce(comptime op: std.builtin.ReduceOp, value: anytype) E", + .snippet = "@reduce(${1:comptime op: std.builtin.ReduceOp}, ${2:value: anytype})", + .documentation = + \\Transforms a [vector](https://ziglang.org/documentation/0.10.1/#Vectors) into a scalar value (of type + \\`E`) by performing a sequential horizontal reduction of its elements using the specified operator `op`. + \\ + \\Not every operator is available for every vector element type: + \\ + \\ - Every operator is available for [integer](https://ziglang.org/documentation/0.10.1/#Integers) vectors. + \\ - `.And`, `.Or`, `.Xor` are additionally available for `bool` vectors, + \\ - `.Min`, `.Max`, `.Add`, `.Mul` are additionally available for [floating point](https://ziglang.org/documentation/0.10.1/#Floats) vectors, + \\Note that `.Add` and `.Mul` reductions on integral types are wrapping; when applied on floating point types the operation associativity is preserved, unless the float mode is set to `Optimized`. + \\ + \\```zig + \\const std = @import("std"); + \\const expect = std.testing.expect; + \\test "vector @reduce" { + \\ const value = @Vector(4, i32){ 1, -1, 1, -1 }; + \\ const result = value > @splat(4, @as(i32, 0)); + \\ // result is { true, false, true, false }; + \\ comptime try expect(@TypeOf(result) == @Vector(4, bool)); + \\ const is_all_true = @reduce(.And, result); + \\ comptime try expect(@TypeOf(is_all_true) == bool); + \\ try expect(is_all_true == false); + \\} + \\``` + , + .arguments = &.{ + "comptime op: std.builtin.ReduceOp", + "value: anytype", + }, + }, + .{ + .name = "@src", + .signature = "@src() std.builtin.SourceLocation", + .snippet = "@src()", + .documentation = + \\Returns a `SourceLocation` struct representing the function's name and location in the source code. This must be called in a function. + \\ + \\```zig + \\const std = @import("std"); + \\const expect = std.testing.expect; + \\test "@src" { + \\ try doTheTest(); + \\} + \\fn doTheTest() !void { + \\ const src = @src(); + \\ try expect(src.line == 9); + \\ try expect(src.column == 17); + \\ try expect(std.mem.endsWith(u8, src.fn_name, "doTheTest")); + \\ try expect(std.mem.endsWith(u8, src.file, "source_location.zig")); + \\} + \\``` + , + .arguments = &.{}, + }, + .{ + .name = "@sqrt", + .signature = "@sqrt(value: anytype) @TypeOf(value)", + .snippet = "@sqrt(${1:value: anytype})", + .documentation = + \\Performs the square root of a floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@sin", + .signature = "@sin(value: anytype) @TypeOf(value)", + .snippet = "@sin(${1:value: anytype})", + .documentation = + \\Sine trigonometric function on a floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@cos", + .signature = "@cos(value: anytype) @TypeOf(value)", + .snippet = "@cos(${1:value: anytype})", + .documentation = + \\Cosine trigonometric function on a floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@tan", + .signature = "@tan(value: anytype) @TypeOf(value)", + .snippet = "@tan(${1:value: anytype})", + .documentation = + \\Tangent trigonometric function on a floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@exp", + .signature = "@exp(value: anytype) @TypeOf(value)", + .snippet = "@exp(${1:value: anytype})", + .documentation = + \\Base-e exponential function on a floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@exp2", + .signature = "@exp2(value: anytype) @TypeOf(value)", + .snippet = "@exp2(${1:value: anytype})", + .documentation = + \\Base-2 exponential function on a floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@log", + .signature = "@log(value: anytype) @TypeOf(value)", + .snippet = "@log(${1:value: anytype})", + .documentation = + \\Returns the natural logarithm of a floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@log2", + .signature = "@log2(value: anytype) @TypeOf(value)", + .snippet = "@log2(${1:value: anytype})", + .documentation = + \\Returns the logarithm to the base 2 of a floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@log10", + .signature = "@log10(value: anytype) @TypeOf(value)", + .snippet = "@log10(${1:value: anytype})", + .documentation = + \\Returns the logarithm to the base 10 of a floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@fabs", + .signature = "@fabs(value: anytype) @TypeOf(value)", + .snippet = "@fabs(${1:value: anytype})", + .documentation = + \\Returns the absolute value of a floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@floor", + .signature = "@floor(value: anytype) @TypeOf(value)", + .snippet = "@floor(${1:value: anytype})", + .documentation = + \\Returns the largest integral value not greater than the given floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@ceil", + .signature = "@ceil(value: anytype) @TypeOf(value)", + .snippet = "@ceil(${1:value: anytype})", + .documentation = + \\Returns the smallest integral value not less than the given floating point number. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@trunc", + .signature = "@trunc(value: anytype) @TypeOf(value)", + .snippet = "@trunc(${1:value: anytype})", + .documentation = + \\Rounds the given floating point number to an integer, towards zero. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@round", + .signature = "@round(value: anytype) @TypeOf(value)", + .snippet = "@round(${1:value: anytype})", + .documentation = + \\Rounds the given floating point number to an integer, away from zero. Uses a dedicated hardware instruction when available. + \\ + \\Supports [Floats](https://ziglang.org/documentation/0.10.1/#Floats) and [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) of floats, with the caveat that + \\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@subWithOverflow", + .signature = "@subWithOverflow(comptime T: type, a: T, b: T, result: *T) bool", + .snippet = "@subWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:b: T}, ${4:result: *T})", + .documentation = + \\Performs `result.* = a - b`. If overflow or underflow occurs, stores the overflowed bits in `result` and returns `true`. If no overflow or underflow occurs, returns `false`. + , + .arguments = &.{ + "comptime T: type", + "a: T", + "b: T", + "result: *T", + }, + }, + .{ + .name = "@tagName", + .signature = "@tagName(value: anytype) [:0]const u8", + .snippet = "@tagName(${1:value: anytype})", + .documentation = + \\Converts an enum value or union value to a string literal representing the name. + \\ + \\If the enum is non-exhaustive and the tag value does not map to a name, it invokes safety-checked [Undefined Behavior](https://ziglang.org/documentation/0.10.1/#Undefined-Behavior). + , + .arguments = &.{ + "value: anytype", + }, + }, + .{ + .name = "@This", + .signature = "@This() type", + .snippet = "@This()", + .documentation = + \\Returns the innermost struct, enum, or union that this function call is inside. This can be useful for an anonymous struct that needs to refer to itself: + \\ + \\```zig + \\const std = @import("std"); + \\const expect = std.testing.expect; + \\test "@This()" { + \\ var items = [_]i32{ 1, 2, 3, 4 }; + \\ const list = List(i32){ .items = items[0..] }; + \\ try expect(list.length() == 4); + \\} + \\fn List(comptime T: type) type { + \\ return struct { + \\ const Self = @This(); + \\ items: []T, + \\ fn length(self: Self) usize { + \\ return self.items.len; + \\ } + \\ }; + \\} + \\``` + \\When `@This()` is used at file scope, it returns a reference to the struct that corresponds to the current file. + , + .arguments = &.{}, + }, + .{ + .name = "@truncate", + .signature = "@truncate(comptime T: type, integer: anytype) T", + .snippet = "@truncate(${1:comptime T: type}, ${2:integer: anytype})", + .documentation = + \\This function truncates bits from an integer type, resulting in a smaller or same-sized integer type. + \\ + \\This function always truncates the significant bits of the integer, regardless of endianness on the target platform. + \\ + \\Calling `@truncate` on a number out of range of the destination type is well defined and working code: + \\ + \\```zig + \\const std = @import("std"); + \\const expect = std.testing.expect; + \\test "integer truncation" { + \\ var a: u16 = 0xabcd; + \\ var b: u8 = @truncate(u8, a); + \\ try expect(b == 0xcd); + \\} + \\``` + \\Use [@intCast](https://ziglang.org/documentation/0.10.1/#intCast) to convert numbers guaranteed to fit the destination type. + , + .arguments = &.{ + "comptime T: type", + "integer: anytype", + }, + }, + .{ + .name = "@Type", + .signature = "@Type(comptime info: std.builtin.Type) type", + .snippet = "@Type(${1:comptime info: std.builtin.Type})", + .documentation = + \\This function is the inverse of [@typeInfo](https://ziglang.org/documentation/0.10.1/#typeInfo). It reifies type information into a `type`. + \\ + \\It is available for the following types: + \\ + \\ - `type` + \\ - `noreturn` + \\ - `void` + \\ - `bool` + \\ - [Integers](https://ziglang.org/documentation/0.10.1/#Integers) - The maximum bit count for an integer type is `65535`. + \\ - [Floats](https://ziglang.org/documentation/0.10.1/#Floats) + \\ - [Pointers](https://ziglang.org/documentation/0.10.1/#Pointers) + \\ - `comptime_int` + \\ - `comptime_float` + \\ - `@TypeOf(undefined)` + \\ - `@TypeOf(null)` + \\ - [Arrays](https://ziglang.org/documentation/0.10.1/#Arrays) + \\ - [Optionals](https://ziglang.org/documentation/0.10.1/#Optionals) + \\ - [Error Set Type](https://ziglang.org/documentation/0.10.1/#Error-Set-Type) + \\ - [Error Union Type](https://ziglang.org/documentation/0.10.1/#Error-Union-Type) + \\ - [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors) + \\ - [opaque](https://ziglang.org/documentation/0.10.1/#opaque) + \\ - [@Frame](https://ziglang.org/documentation/0.10.1/#Frame) + \\ - `anyframe` + \\ - [struct](https://ziglang.org/documentation/0.10.1/#struct) + \\ - [enum](https://ziglang.org/documentation/0.10.1/#enum) + \\ - [Enum Literals](https://ziglang.org/documentation/0.10.1/#Enum-Literals) + \\ - [union](https://ziglang.org/documentation/0.10.1/#union) + \\For these types, `@Type` is not available: + \\ + \\ - [Functions](https://ziglang.org/documentation/0.10.1/#Functions) + \\ - BoundFn + , + .arguments = &.{ + "comptime info: std.builtin.Type", + }, + }, + .{ + .name = "@typeInfo", + .signature = "@typeInfo(comptime T: type) std.builtin.Type", + .snippet = "@typeInfo(${1:comptime T: type})", + .documentation = + \\Provides type reflection. + \\ + \\Type information of [structs](https://ziglang.org/documentation/0.10.1/#struct), [unions](https://ziglang.org/documentation/0.10.1/#union), [enums](https://ziglang.org/documentation/0.10.1/#enum), and [error sets](https://ziglang.org/documentation/0.10.1/#Error-Set-Type) has fields which are guaranteed to be in the same order as appearance in the source file. + \\ + \\Type information of [structs](https://ziglang.org/documentation/0.10.1/#struct), [unions](https://ziglang.org/documentation/0.10.1/#union), [enums](https://ziglang.org/documentation/0.10.1/#enum), and [opaques](https://ziglang.org/documentation/0.10.1/#opaque) has declarations, which are also guaranteed to be in the same order as appearance in the source file. + , + .arguments = &.{ + "comptime T: type", + }, + }, + .{ + .name = "@typeName", + .signature = "@typeName(T: type) *const [N:0]u8", + .snippet = "@typeName(${1:T: type})", + .documentation = + \\This function returns the string representation of a type, as an array. It is equivalent to a string literal of the type name. The returned type name is fully qualified with the parent namespace included as part of the type name with a series of dots. + , + .arguments = &.{ + "T: type", + }, + }, + .{ + .name = "@TypeOf", + .signature = "@TypeOf(...) type", + .snippet = "@TypeOf(${1:...})", + .documentation = + \\`@TypeOf` is a special builtin function that takes any (nonzero) number of expressions as parameters and returns the type of the result, using [Peer Type Resolution](https://ziglang.org/documentation/0.10.1/#Peer-Type-Resolution). + \\ + \\The expressions are evaluated, however they are guaranteed to have no + \\**runtime** side-effects: + \\ + \\```zig + \\const std = @import("std"); + \\const expect = std.testing.expect; + \\test "no runtime side effects" { + \\ var data: i32 = 0; + \\ const T = @TypeOf(foo(i32, &data)); + \\ comptime try expect(T == i32); + \\ try expect(data == 0); + \\} + \\fn foo(comptime T: type, ptr: *T) T { + \\ ptr.* += 1; + \\ return ptr.*; + \\} + \\``` + , + .arguments = &.{ + "...", + }, + }, + .{ + .name = "@unionInit", + .signature = "@unionInit(comptime Union: type, comptime active_field_name: []const u8, init_expr) Union", + .snippet = "@unionInit(${1:comptime Union: type}, ${2:comptime active_field_name: []const u8}, ${3:init_expr})", + .documentation = + \\This is the same thing as [union](https://ziglang.org/documentation/0.10.1/#union) initialization syntax, except that the field name is a [comptime](https://ziglang.org/documentation/0.10.1/#comptime)-known value rather than an identifier token. + \\ + \\`@unionInit` forwards its [result location](https://ziglang.org/documentation/0.10.1/#Result-Location-Semantics) to `init_expr`. + , + .arguments = &.{ + "comptime Union: type", + "comptime active_field_name: []const u8", + "init_expr", + }, + }, + .{ + .name = "@Vector", + .signature = "@Vector(len: comptime_int, Element: type) type", + .snippet = "@Vector(${1:len: comptime_int}, ${2:Element: type})", + .documentation = + \\Creates [Vectors](https://ziglang.org/documentation/0.10.1/#Vectors). + , + .arguments = &.{ + "len: comptime_int", + "Element: type", + }, + }, +}; + +// DO NOT EDIT diff --git a/src/data/data.zig b/src/data/data.zig index 21257bf..dc445f3 100644 --- a/src/data/data.zig +++ b/src/data/data.zig @@ -9,4 +9,5 @@ pub usingnamespace switch (build_options.data_version) { .@"0.9.0" => @import("0.9.0.zig"), .@"0.9.1" => @import("0.9.1.zig"), .@"0.10.0" => @import("0.10.0.zig"), + .@"0.10.1" => @import("0.10.1.zig"), }; diff --git a/src/data/master.zig b/src/data/master.zig index cc7d3e8..2a57da3 100644 --- a/src/data/master.zig +++ b/src/data/master.zig @@ -1600,7 +1600,7 @@ pub const builtins = [_]Builtin{ \\ try expect(src.line == 9); \\ try expect(src.column == 17); \\ try expect(std.mem.endsWith(u8, src.fn_name, "doTheTest")); - \\ try expect(std.mem.endsWith(u8, src.file, "source_location.zig")); + \\ try expect(std.mem.endsWith(u8, src.file, "test_src_builtin.zig")); \\} \\``` , @@ -1914,7 +1914,7 @@ pub const builtins = [_]Builtin{ \\ - [enum](https://ziglang.org/documentation/master/#enum) \\ - [Enum Literals](https://ziglang.org/documentation/master/#Enum-Literals) \\ - [union](https://ziglang.org/documentation/master/#union) - \\`@Type` is not available for [Functions](https://ziglang.org/documentation/master/#Functions). + \\ - [Functions](https://ziglang.org/documentation/master/#Functions) , .arguments = &.{ "comptime info: std.builtin.Type", diff --git a/src/shared.zig b/src/shared.zig index fac7994..bdf4646 100644 --- a/src/shared.zig +++ b/src/shared.zig @@ -9,4 +9,5 @@ pub const ZigVersion = enum { @"0.9.0", @"0.9.1", @"0.10.0", + @"0.10.1", }; From 7b3cc1d6d42c99a79229b3eac7d723a61c8af91e Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Wed, 1 Feb 2023 04:41:39 +0100 Subject: [PATCH 07/12] Optimize inlay hints (#948) * optimize inlay hints * update iterateChildren * add tests for nodesAtLoc --- src/Server.zig | 46 +++- src/ast.zig | 452 +++++++++++++++++++++++++++++++- src/inlay_hints.zig | 587 ++++++++---------------------------------- src/offsets.zig | 23 ++ src/zls.zig | 1 + tests/tests.zig | 1 + tests/utility/ast.zig | 76 ++++++ 7 files changed, 685 insertions(+), 501 deletions(-) create mode 100644 tests/utility/ast.zig diff --git a/src/Server.zig b/src/Server.zig index a8969e8..f6dba9f 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -2493,6 +2493,7 @@ fn inlayHintHandler(server: *Server, request: types.InlayHintParams) Error!?[]ty const handle = server.document_store.getHandle(request.textDocument.uri) orelse return null; const hover_kind: types.MarkupKind = if (server.client_capabilities.hover_supports_md) .markdown else .plaintext; + const loc = offsets.rangeToLoc(handle.text, request.range, server.offset_encoding); // TODO cache hints per document // because the function could be stored in a different document @@ -2503,27 +2504,44 @@ fn inlayHintHandler(server: *Server, request: types.InlayHintParams) Error!?[]ty server.config.*, &server.document_store, handle, - request.range, + loc, hover_kind, - server.offset_encoding, ); - // and only convert and return all hints in range for every request - var visible_hints = hints; + const helper = struct { + fn lessThan(_: void, lhs: inlay_hints.InlayHint, rhs: inlay_hints.InlayHint) bool { + return lhs.token_index < rhs.token_index; + } + }; - // small_hints should roughly be sorted by position + std.sort.sort(inlay_hints.InlayHint, hints, {}, helper.lessThan); + + var last_index: usize = 0; + var last_position: types.Position = .{ .line = 0, .character = 0 }; + + var converted_hints = try server.arena.allocator().alloc(types.InlayHint, hints.len); for (hints) |hint, i| { - if (isPositionBefore(hint.position, request.range.start)) continue; - visible_hints = hints[i..]; - break; - } - for (visible_hints) |hint, i| { - if (isPositionBefore(hint.position, request.range.end)) continue; - visible_hints = visible_hints[0..i]; - break; + const index = offsets.tokenToIndex(handle.tree, hint.token_index); + const position = offsets.advancePosition( + handle.tree.source, + last_position, + last_index, + index, + server.offset_encoding, + ); + defer last_index = index; + defer last_position = position; + converted_hints[i] = types.InlayHint{ + .position = position, + .label = .{ .string = hint.label }, + .kind = hint.kind, + .tooltip = .{ .MarkupContent = hint.tooltip }, + .paddingLeft = false, + .paddingRight = true, + }; } - return visible_hints; + return converted_hints; } fn codeActionHandler(server: *Server, request: types.CodeActionParams) Error!?[]types.CodeAction { diff --git a/src/ast.zig b/src/ast.zig index 71cdf61..8caa210 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -3,6 +3,7 @@ //! when there are parser errors. const std = @import("std"); +const offsets = @import("offsets.zig"); const Ast = std.zig.Ast; const Node = Ast.Node; const full = Ast.full; @@ -545,7 +546,7 @@ pub fn lastToken(tree: Ast, node: Ast.Node.Index) Ast.TokenIndex { }, .container_decl_arg, .container_decl_arg_trailing, - => { + => { const members = tree.extraData(datas[n].rhs, Node.SubRange); if (members.end - members.start == 0) { end_offset += 3; // for the rparen + lbrace + rbrace @@ -1140,3 +1141,452 @@ pub fn nextFnParam(it: *Ast.full.FnProto.Iterator) ?Ast.full.FnProto.Param { it.tok_flag = false; } } + +/// returns an Iterator that yields every child of the given node. +/// see `nodeChildrenAlloc` for a non-iterator allocating variant. +/// the order in which children are given corresponds to the order in which they are found in the source text +pub fn iterateChildren( + tree: Ast, + node: Ast.Node.Index, + context: anytype, + comptime Error: type, + comptime callback: fn (@TypeOf(context), Ast.Node.Index) Error!void, +) Error!void { + const node_tags = tree.nodes.items(.tag); + const node_data = tree.nodes.items(.data); + + if (node > tree.nodes.len) return; + + const tag = node_tags[node]; + switch (tag) { + .@"usingnamespace", + .field_access, + .unwrap_optional, + .bool_not, + .negation, + .bit_not, + .negation_wrap, + .address_of, + .@"try", + .@"await", + .optional_type, + .deref, + .@"suspend", + .@"resume", + .@"return", + .grouped_expression, + .@"comptime", + .@"nosuspend", + .asm_simple, + => { + try callback(context, node_data[node].lhs); + }, + + .test_decl, + .@"errdefer", + .@"defer", + .@"break", + .anyframe_type, + => { + try callback(context, node_data[node].rhs); + }, + + .@"catch", + .equal_equal, + .bang_equal, + .less_than, + .greater_than, + .less_or_equal, + .greater_or_equal, + .assign_mul, + .assign_div, + .assign_mod, + .assign_add, + .assign_sub, + .assign_shl, + .assign_shl_sat, + .assign_shr, + .assign_bit_and, + .assign_bit_xor, + .assign_bit_or, + .assign_mul_wrap, + .assign_add_wrap, + .assign_sub_wrap, + .assign_mul_sat, + .assign_add_sat, + .assign_sub_sat, + .assign, + .merge_error_sets, + .mul, + .div, + .mod, + .array_mult, + .mul_wrap, + .mul_sat, + .add, + .sub, + .array_cat, + .add_wrap, + .sub_wrap, + .add_sat, + .sub_sat, + .shl, + .shl_sat, + .shr, + .bit_and, + .bit_xor, + .bit_or, + .@"orelse", + .bool_and, + .bool_or, + .array_type, + .array_access, + .array_init_one, + .array_init_one_comma, + .array_init_dot_two, + .array_init_dot_two_comma, + .struct_init_one, + .struct_init_one_comma, + .struct_init_dot_two, + .struct_init_dot_two_comma, + .call_one, + .call_one_comma, + .async_call_one, + .async_call_one_comma, + .switch_range, + .builtin_call_two, + .builtin_call_two_comma, + .container_decl_two, + .container_decl_two_trailing, + .tagged_union_two, + .tagged_union_two_trailing, + .container_field_init, + .container_field_align, + .block_two, + .block_two_semicolon, + .error_union, + => { + try callback(context, node_data[node].lhs); + try callback(context, node_data[node].rhs); + }, + + .root, + .array_init_dot, + .array_init_dot_comma, + .struct_init_dot, + .struct_init_dot_comma, + .builtin_call, + .builtin_call_comma, + .container_decl, + .container_decl_trailing, + .tagged_union, + .tagged_union_trailing, + .block, + .block_semicolon, + => { + for (tree.extra_data[node_data[node].lhs..node_data[node].rhs]) |child| { + try callback(context, child); + } + }, + + .global_var_decl, + .local_var_decl, + .simple_var_decl, + .aligned_var_decl, + => { + const var_decl = tree.fullVarDecl(node).?.ast; + try callback(context, var_decl.type_node); + try callback(context, var_decl.align_node); + try callback(context, var_decl.addrspace_node); + try callback(context, var_decl.section_node); + try callback(context, var_decl.init_node); + }, + + .array_type_sentinel => { + const array_type = tree.arrayTypeSentinel(node).ast; + try callback(context, array_type.elem_count); + try callback(context, array_type.sentinel); + try callback(context, array_type.elem_type); + }, + + .ptr_type_aligned, + .ptr_type_sentinel, + .ptr_type, + .ptr_type_bit_range, + => { + const ptr_type = fullPtrType(tree, node).?.ast; + try callback(context, ptr_type.sentinel); + try callback(context, ptr_type.align_node); + try callback(context, ptr_type.bit_range_start); + try callback(context, ptr_type.bit_range_end); + try callback(context, ptr_type.addrspace_node); + try callback(context, ptr_type.child_type); + }, + + .slice_open, + .slice, + .slice_sentinel, + => { + const slice = tree.fullSlice(node).?; + try callback(context, slice.ast.sliced); + try callback(context, slice.ast.start); + try callback(context, slice.ast.end); + try callback(context, slice.ast.sentinel); + }, + + .array_init, + .array_init_comma, + => { + const array_init = tree.arrayInit(node).ast; + try callback(context, array_init.type_expr); + for (array_init.elements) |child| { + try callback(context, child); + } + }, + + .struct_init, + .struct_init_comma, + => { + const struct_init = tree.structInit(node).ast; + try callback(context, struct_init.type_expr); + for (struct_init.fields) |child| { + try callback(context, child); + } + }, + + .call, + .call_comma, + .async_call, + .async_call_comma, + => { + const call = tree.callFull(node).ast; + try callback(context, call.fn_expr); + for (call.params) |child| { + try callback(context, child); + } + }, + + .@"switch", + .switch_comma, + => { + const cond = node_data[node].lhs; + const extra = tree.extraData(node_data[node].rhs, Ast.Node.SubRange); + const cases = tree.extra_data[extra.start..extra.end]; + try callback(context, cond); + for (cases) |child| { + try callback(context, child); + } + }, + + .switch_case_one, + .switch_case_inline_one, + .switch_case, + .switch_case_inline, + => { + const switch_case = tree.fullSwitchCase(node).?.ast; + for (switch_case.values) |child| { + try callback(context, child); + } + try callback(context, switch_case.target_expr); + }, + + .while_simple, + .while_cont, + .@"while", + .for_simple, + .@"for", + => { + const while_ast = fullWhile(tree, node).?.ast; + try callback(context, while_ast.cond_expr); + try callback(context, while_ast.cont_expr); + try callback(context, while_ast.then_expr); + try callback(context, while_ast.else_expr); + }, + + .@"if", + .if_simple, + => { + const if_ast = ifFull(tree, node).ast; + try callback(context, if_ast.cond_expr); + try callback(context, if_ast.then_expr); + try callback(context, if_ast.else_expr); + }, + + .fn_proto_simple, + .fn_proto_multi, + .fn_proto_one, + .fn_proto, + .fn_decl, + => { + var buffer: [1]Node.Index = undefined; + const fn_proto = tree.fullFnProto(&buffer, node).?; + + for (fn_proto.ast.params) |child| { + try callback(context, child); + } + try callback(context, fn_proto.ast.align_expr); + try callback(context, fn_proto.ast.addrspace_expr); + try callback(context, fn_proto.ast.section_expr); + try callback(context, fn_proto.ast.callconv_expr); + try callback(context, fn_proto.ast.return_type); + }, + + .container_decl_arg, + .container_decl_arg_trailing, + => { + const decl = tree.containerDeclArg(node).ast; + try callback(context, decl.arg); + for (decl.members) |child| { + try callback(context, child); + } + }, + + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + => { + const decl = tree.taggedUnionEnumTag(node).ast; + try callback(context, decl.arg); + for (decl.members) |child| { + try callback(context, child); + } + }, + + .container_field => { + const field = tree.containerField(node).ast; + try callback(context, field.type_expr); + try callback(context, field.align_expr); + try callback(context, field.value_expr); + }, + + .@"asm" => { + const asm_ast = tree.asmFull(node).ast; + try callback(context, asm_ast.template); + for (asm_ast.items) |child| { + try callback(context, child); + } + }, + + .asm_output, + .asm_input, + => {}, // TODO + + .@"continue", + .anyframe_literal, + .char_literal, + .number_literal, + .unreachable_literal, + .identifier, + .enum_literal, + .string_literal, + .multiline_string_literal, + .error_set_decl, + .error_value, + => {}, + } +} + +/// returns an Iterator that recursively yields every child of the given node. +/// see `nodeChildrenRecursiveAlloc` for a non-iterator allocating variant. +pub fn iterateChildrenRecursive( + tree: Ast, + node: Ast.Node.Index, + context: anytype, + comptime Error: type, + comptime callback: fn (@TypeOf(context), Ast.Node.Index) Error!void, +) Error!void { + const RecursiveContext = struct { + tree: Ast, + context: @TypeOf(context), + + fn recursive_callback(self: @This(), child_node: Ast.Node.Index) Error!void { + if (child_node == 0) return; + try callback(self.context, child_node); + try iterateChildrenRecursive(self.tree, child_node, self.context, Error, callback); + } + }; + + try iterateChildren(tree, node, RecursiveContext{ + .tree = tree, + .context = context, + }, Error, RecursiveContext.recursive_callback); +} + +/// returns the children of the given node. +/// see `iterateChildren` for a callback variant +/// caller owns the returned memory +pub fn nodeChildrenAlloc(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index) error{OutOfMemory}![]Ast.Node.Index { + const Context = struct { + children: *std.ArrayList(Ast.Node.Index), + fn callback(self: @This(), child_node: Ast.Node.Index) error{OutOfMemory}!void { + if (child_node == 0) return; + try self.children.append(child_node); + } + }; + + var children = std.ArrayList(Ast.Node.Index).init(allocator); + errdefer children.deinit(); + try iterateChildren(tree, node, Context{ .children = &children }, error{OutOfMemory}, Context.callback); + return children.toOwnedSlice(); +} + +/// returns the children of the given node. +/// see `iterateChildrenRecursive` for a callback variant +/// caller owns the returned memory +pub fn nodeChildrenRecursiveAlloc(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index) error{OutOfMemory}![]Ast.Node.Index { + const Context = struct { + children: *std.ArrayList(Ast.Node.Index), + fn callback(self: @This(), child_node: Ast.Node.Index) error{OutOfMemory}!void { + if (child_node == 0) return; + try self.children.append(child_node); + } + }; + + var children = std.ArrayList(Ast.Node.Index).init(allocator); + errdefer children.deinit(); + try iterateChildrenRecursive(tree, node, .{ .children = &children }, Context.callback); + return children.toOwnedSlice(allocator); +} + +/// returns a list of nodes that together encloses the given source code range +/// caller owns the returned memory +pub fn nodesAtLoc(allocator: std.mem.Allocator, tree: Ast, loc: offsets.Loc) error{OutOfMemory}![]Ast.Node.Index { + std.debug.assert(loc.start <= loc.end and loc.end <= tree.source.len); + + var nodes = std.ArrayListUnmanaged(Ast.Node.Index){}; + errdefer nodes.deinit(allocator); + var parent: Ast.Node.Index = 0; // root node + + try nodes.ensureTotalCapacity(allocator, 32); + + while (true) { + const children = try nodeChildrenAlloc(allocator, tree, parent); + defer allocator.free(children); + + var children_loc: ?offsets.Loc = null; + for (children) |child_node| { + const child_loc = offsets.nodeToLoc(tree, child_node); + + const merge_child = offsets.locIntersect(loc, child_loc) or offsets.locInside(child_loc, loc); + + if (merge_child) { + children_loc = if (children_loc) |l| offsets.locMerge(l, child_loc) else child_loc; + try nodes.append(allocator, child_node); + } else { + if (nodes.items.len != 0) break; + } + } + + if (children_loc == null or !offsets.locInside(loc, children_loc.?)) { + nodes.clearRetainingCapacity(); + nodes.appendAssumeCapacity(parent); // capacity is never 0 + return try nodes.toOwnedSlice(allocator); + } + + if (nodes.items.len == 1) { + parent = nodes.items[0]; + nodes.clearRetainingCapacity(); + } else { + return try nodes.toOwnedSlice(allocator); + } + } +} diff --git a/src/inlay_hints.zig b/src/inlay_hints.zig index 4f88aa9..b12c08b 100644 --- a/src/inlay_hints.zig +++ b/src/inlay_hints.zig @@ -16,123 +16,112 @@ const Config = @import("Config.zig"); /// non-configurable at runtime pub const inlay_hints_exclude_builtins: []const u8 = &.{}; -/// max number of children in a declaration/array-init/struct-init or similar -/// that will not get a visibility check -pub const inlay_hints_max_inline_children = 12; - -/// checks whether node is inside the range -fn isNodeInRange(tree: Ast, node: Ast.Node.Index, range: types.Range) bool { - const endLocation = tree.tokenLocation(0, ast.lastToken(tree, node)); - if (endLocation.line < range.start.line) return false; - - const beginLocation = tree.tokenLocation(0, tree.firstToken(node)); - if (beginLocation.line > range.end.line) return false; - - return true; -} +pub const InlayHint = struct { + token_index: Ast.TokenIndex, + label: []const u8, + kind: types.InlayHintKind, + tooltip: types.MarkupContent, +}; const Builder = struct { - arena: std.mem.Allocator, + arena: *std.heap.ArenaAllocator, + store: *DocumentStore, config: *const Config, handle: *const DocumentStore.Handle, - hints: std.ArrayListUnmanaged(types.InlayHint), + hints: std.ArrayListUnmanaged(InlayHint), hover_kind: types.MarkupKind, - encoding: offsets.Encoding, - fn appendParameterHint(self: *Builder, position: types.Position, label: []const u8, tooltip: []const u8, tooltip_noalias: bool, tooltip_comptime: bool) !void { - // TODO allocation could be avoided by extending InlayHint.jsonStringify + fn appendParameterHint(self: *Builder, token_index: Ast.TokenIndex, label: []const u8, tooltip: []const u8, tooltip_noalias: bool, tooltip_comptime: bool) !void { // adding tooltip_noalias & tooltip_comptime to InlayHint should be enough const tooltip_text = blk: { if (tooltip.len == 0) break :blk ""; const prefix = if (tooltip_noalias) if (tooltip_comptime) "noalias comptime " else "noalias " else if (tooltip_comptime) "comptime " else ""; if (self.hover_kind == .markdown) { - break :blk try std.fmt.allocPrint(self.arena, "```zig\n{s}{s}\n```", .{ prefix, tooltip }); + break :blk try std.fmt.allocPrint(self.arena.allocator(), "```zig\n{s}{s}\n```", .{ prefix, tooltip }); } - break :blk try std.fmt.allocPrint(self.arena, "{s}{s}", .{ prefix, tooltip }); + break :blk try std.fmt.allocPrint(self.arena.allocator(), "{s}{s}", .{ prefix, tooltip }); }; - try self.hints.append(self.arena, .{ - .position = position, - .label = .{ .string = try std.fmt.allocPrint(self.arena, "{s}:", .{label}) }, - .kind = types.InlayHintKind.Parameter, - .tooltip = .{ .MarkupContent = .{ + try self.hints.append(self.arena.allocator(), .{ + .token_index = token_index, + .label = try std.fmt.allocPrint(self.arena.allocator(), "{s}:", .{label}), + .kind = .Parameter, + .tooltip = .{ .kind = self.hover_kind, .value = tooltip_text, - } }, - .paddingLeft = false, - .paddingRight = true, + }, }); } - fn toOwnedSlice(self: *Builder) error{OutOfMemory}![]types.InlayHint { - return self.hints.toOwnedSlice(self.arena); + fn toOwnedSlice(self: *Builder) error{OutOfMemory}![]InlayHint { + return self.hints.toOwnedSlice(self.arena.allocator()); } }; /// `call` is the function call /// `decl_handle` should be a function protototype /// writes parameter hints into `builder.hints` -fn writeCallHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: *DocumentStore, call: Ast.full.Call, decl_handle: analysis.DeclWithHandle) !void { +fn writeCallHint(builder: *Builder, call: Ast.full.Call, decl_handle: analysis.DeclWithHandle) !void { const handle = builder.handle; const tree = handle.tree; const decl = decl_handle.decl; const decl_tree = decl_handle.handle.tree; - switch (decl.*) { - .ast_node => |fn_node| { - var buffer: [1]Ast.Node.Index = undefined; - if (decl_tree.fullFnProto(&buffer, fn_node)) |fn_proto| { - var i: usize = 0; - var it = fn_proto.iterate(&decl_tree); + const fn_node = switch (decl.*) { + .ast_node => |fn_node| fn_node, + else => return, + }; - if (try analysis.hasSelfParam(arena, store, decl_handle.handle, fn_proto)) { - _ = ast.nextFnParam(&it); - } + var buffer: [1]Ast.Node.Index = undefined; + const fn_proto = decl_tree.fullFnProto(&buffer, fn_node) orelse return; - while (ast.nextFnParam(&it)) |param| : (i += 1) { - if (i >= call.ast.params.len) break; - const name_token = param.name_token orelse continue; - const name = decl_tree.tokenSlice(name_token); + var i: usize = 0; + var it = fn_proto.iterate(&decl_tree); - if (builder.config.inlay_hints_hide_redundant_param_names or builder.config.inlay_hints_hide_redundant_param_names_last_token) { - const last_param_token = tree.lastToken(call.ast.params[i]); - const param_name = tree.tokenSlice(last_param_token); + if (try analysis.hasSelfParam(builder.arena, builder.store, decl_handle.handle, fn_proto)) { + _ = ast.nextFnParam(&it); + } - if (std.mem.eql(u8, param_name, name)) { - if (tree.firstToken(call.ast.params[i]) == last_param_token) { - if (builder.config.inlay_hints_hide_redundant_param_names) - continue; - } else { - if (builder.config.inlay_hints_hide_redundant_param_names_last_token) - continue; - } - } - } + while (ast.nextFnParam(&it)) |param| : (i += 1) { + if (i >= call.ast.params.len) break; + const name_token = param.name_token orelse continue; + const name = decl_tree.tokenSlice(name_token); - const token_tags = decl_tree.tokens.items(.tag); + if (builder.config.inlay_hints_hide_redundant_param_names or builder.config.inlay_hints_hide_redundant_param_names_last_token) { + const last_param_token = tree.lastToken(call.ast.params[i]); + const param_name = tree.tokenSlice(last_param_token); - const no_alias = if (param.comptime_noalias) |t| token_tags[t] == .keyword_noalias or token_tags[t - 1] == .keyword_noalias else false; - const comp_time = if (param.comptime_noalias) |t| token_tags[t] == .keyword_comptime or token_tags[t - 1] == .keyword_comptime else false; - - const tooltip = if (param.anytype_ellipsis3) |token| - if (token_tags[token] == .keyword_anytype) "anytype" else "" - else - offsets.nodeToSlice(decl_tree, param.type_expr); - - try builder.appendParameterHint( - offsets.tokenToPosition(tree, tree.firstToken(call.ast.params[i]), builder.encoding), - name, - tooltip, - no_alias, - comp_time, - ); + if (std.mem.eql(u8, param_name, name)) { + if (tree.firstToken(call.ast.params[i]) == last_param_token) { + if (builder.config.inlay_hints_hide_redundant_param_names) + continue; + } else { + if (builder.config.inlay_hints_hide_redundant_param_names_last_token) + continue; } } - }, - else => {}, + } + + const token_tags = decl_tree.tokens.items(.tag); + + const no_alias = if (param.comptime_noalias) |t| token_tags[t] == .keyword_noalias or token_tags[t - 1] == .keyword_noalias else false; + const comp_time = if (param.comptime_noalias) |t| token_tags[t] == .keyword_comptime or token_tags[t - 1] == .keyword_comptime else false; + + const tooltip = if (param.anytype_ellipsis3) |token| + if (token_tags[token] == .keyword_anytype) "anytype" else "" + else + offsets.nodeToSlice(decl_tree, param.type_expr); + + try builder.appendParameterHint( + tree.firstToken(call.ast.params[i]), + name, + tooltip, + no_alias, + comp_time, + ); } } @@ -164,7 +153,7 @@ fn writeBuiltinHint(builder: *Builder, parameters: []const Ast.Node.Index, argum } try builder.appendParameterHint( - offsets.tokenToPosition(tree, tree.firstToken(parameters[i]), builder.encoding), + tree.firstToken(parameters[i]), label orelse "", std.mem.trim(u8, type_expr, " \t\n"), no_alias, @@ -174,7 +163,7 @@ fn writeBuiltinHint(builder: *Builder, parameters: []const Ast.Node.Index, argum } /// takes a Ast.full.Call (a function call), analysis its function expression, finds its declaration and writes parameter hints into `builder.hints` -fn writeCallNodeHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: *DocumentStore, call: Ast.full.Call) !void { +fn writeCallNodeHint(builder: *Builder, call: Ast.full.Call) !void { if (call.ast.params.len == 0) return; if (builder.config.inlay_hints_exclude_single_argument and call.ast.params.len == 1) return; @@ -187,14 +176,11 @@ fn writeCallNodeHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: switch (node_tags[call.ast.fn_expr]) { .identifier => { - const location = tree.tokenLocation(0, main_tokens[call.ast.fn_expr]); + const source_index = offsets.tokenToIndex(tree, main_tokens[call.ast.fn_expr]); + const name = offsets.tokenToSlice(tree, main_tokens[call.ast.fn_expr]); - const absolute_index = location.line_start + location.column; - - const name = tree.tokenSlice(main_tokens[call.ast.fn_expr]); - - if (try analysis.lookupSymbolGlobal(store, arena, handle, name, absolute_index)) |decl_handle| { - try writeCallHint(builder, arena, store, call, decl_handle); + if (try analysis.lookupSymbolGlobal(builder.store, builder.arena, handle, name, source_index)) |decl_handle| { + try writeCallHint(builder, call, decl_handle); } }, .field_access => { @@ -205,23 +191,23 @@ fn writeCallNodeHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: const start = offsets.tokenToIndex(tree, lhsToken); const rhs_loc = offsets.tokenToLoc(tree, rhsToken); - var held_range = try arena.allocator().dupeZ(u8, handle.text[start..rhs_loc.end]); + var held_range = try builder.arena.allocator().dupeZ(u8, handle.text[start..rhs_loc.end]); var tokenizer = std.zig.Tokenizer.init(held_range); // note: we have the ast node, traversing it would probably yield better results // than trying to re-tokenize and re-parse it - if (try analysis.getFieldAccessType(store, arena, handle, rhs_loc.end, &tokenizer)) |result| { + if (try analysis.getFieldAccessType(builder.store, builder.arena, handle, rhs_loc.end, &tokenizer)) |result| { const container_handle = result.unwrapped orelse result.original; switch (container_handle.type.data) { .other => |container_handle_node| { if (try analysis.lookupSymbolContainer( - store, - arena, + builder.store, + builder.arena, .{ .node = container_handle_node, .handle = container_handle.handle }, tree.tokenSlice(rhsToken), true, )) |decl_handle| { - try writeCallHint(builder, arena, store, call, decl_handle); + try writeCallHint(builder, call, decl_handle); } }, else => {}, @@ -234,44 +220,18 @@ 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(.auto, writeNodeInlayHint, args); - } -} - -/// iterates over the ast and writes parameter hints into `builder.hints` for every function call and builtin call -/// nodes outside the given range are excluded -fn writeNodeInlayHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: *DocumentStore, maybe_node: ?Ast.Node.Index, range: types.Range) error{OutOfMemory}!void { - const node = maybe_node orelse return; - +fn writeNodeInlayHint( + builder: *Builder, + node: Ast.Node.Index, +) error{OutOfMemory}!void { const handle = builder.handle; const tree = handle.tree; const node_tags = tree.nodes.items(.tag); - const node_data = tree.nodes.items(.data); const main_tokens = tree.nodes.items(.main_token); - // 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(); - const tag = node_tags[node]; - // NOTE traversing the ast instead of iterating over all nodes allows using visibility - // checks based on the given range which reduce runtimes by orders of magnitude for large files switch (tag) { - .root => unreachable, .call_one, .call_one_comma, .async_call_one, @@ -283,406 +243,61 @@ fn writeNodeInlayHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: => { var params: [1]Ast.Node.Index = undefined; const call = tree.fullCall(¶ms, node).?; - try writeCallNodeHint(builder, arena, store, call); - - for (call.ast.params) |param| { - if (call.ast.params.len > inlay_hints_max_inline_children) { - if (!isNodeInRange(tree, param, range)) continue; - } - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, param, range }); - } + try writeCallNodeHint(builder, call); }, .builtin_call_two, .builtin_call_two_comma, .builtin_call, .builtin_call_comma, - => { + => blk: { var buffer: [2]Ast.Node.Index = undefined; const params = ast.builtinCallParams(tree, node, &buffer).?; - if (builder.config.inlay_hints_show_builtin and params.len > 1) { - const name = tree.tokenSlice(main_tokens[node]); + if (!builder.config.inlay_hints_show_builtin or params.len <= 1) break :blk; - outer: for (data.builtins) |builtin| { - if (!std.mem.eql(u8, builtin.name, name)) continue; + const name = tree.tokenSlice(main_tokens[node]); - for (inlay_hints_exclude_builtins) |builtin_name| { - if (std.mem.eql(u8, builtin_name, name)) break :outer; - } + outer: for (data.builtins) |builtin| { + if (!std.mem.eql(u8, builtin.name, name)) continue; - try writeBuiltinHint(builder, params, builtin.arguments); - } - } - - for (params) |param| { - if (params.len > inlay_hints_max_inline_children) { - if (!isNodeInRange(tree, param, range)) continue; + for (inlay_hints_exclude_builtins) |builtin_name| { + if (std.mem.eql(u8, builtin_name, name)) break :outer; } - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, param, range }); + try writeBuiltinHint(builder, params, builtin.arguments); } }, - - .optional_type, - .array_type, - .@"continue", - .anyframe_type, - .anyframe_literal, - .char_literal, - .number_literal, - .unreachable_literal, - .identifier, - .enum_literal, - .string_literal, - .multiline_string_literal, - .error_set_decl, - => {}, - - .array_type_sentinel => { - const array_type = tree.arrayTypeSentinel(node); - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, array_type.ast.sentinel, range }); - }, - - .ptr_type_aligned, - .ptr_type_sentinel, - .ptr_type, - .ptr_type_bit_range, - => { - const ptr_type: Ast.full.PtrType = ast.fullPtrType(tree, node).?; - - if (ptr_type.ast.sentinel != 0) { - return try callWriteNodeInlayHint(allocator, .{ builder, arena, store, ptr_type.ast.sentinel, range }); - } - - if (ptr_type.ast.align_node != 0) { - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, ptr_type.ast.align_node, range }); - - if (ptr_type.ast.bit_range_start != 0) { - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, ptr_type.ast.bit_range_start, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, ptr_type.ast.bit_range_end, range }); - } - } - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, ptr_type.ast.child_type, range }); - }, - - .@"usingnamespace", - .field_access, - .unwrap_optional, - .bool_not, - .negation, - .bit_not, - .negation_wrap, - .address_of, - .@"try", - .@"await", - .deref, - .@"suspend", - .@"resume", - .@"return", - .grouped_expression, - .@"comptime", - .@"nosuspend", - => try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].lhs, range }), - - .test_decl, - .global_var_decl, - .local_var_decl, - .simple_var_decl, - .aligned_var_decl, - .@"errdefer", - .@"defer", - .@"break", - => try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].rhs, range }), - - .@"catch", - .equal_equal, - .bang_equal, - .less_than, - .greater_than, - .less_or_equal, - .greater_or_equal, - .assign_mul, - .assign_div, - .assign_mod, - .assign_add, - .assign_sub, - .assign_shl, - .assign_shl_sat, - .assign_shr, - .assign_bit_and, - .assign_bit_xor, - .assign_bit_or, - .assign_mul_wrap, - .assign_add_wrap, - .assign_sub_wrap, - .assign_mul_sat, - .assign_add_sat, - .assign_sub_sat, - .assign, - .merge_error_sets, - .mul, - .div, - .mod, - .array_mult, - .mul_wrap, - .mul_sat, - .add, - .sub, - .array_cat, - .add_wrap, - .sub_wrap, - .add_sat, - .sub_sat, - .shl, - .shl_sat, - .shr, - .bit_and, - .bit_xor, - .bit_or, - .@"orelse", - .bool_and, - .bool_or, - .array_access, - .switch_range, - .error_union, - => { - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].lhs, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].rhs, range }); - }, - - .slice_open, - .slice, - .slice_sentinel, - => { - const slice: Ast.full.Slice = tree.fullSlice(node).?; - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, slice.ast.sliced, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, slice.ast.start, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, slice.ast.end, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, slice.ast.sentinel, range }); - }, - - .array_init_one, - .array_init_one_comma, - .array_init_dot_two, - .array_init_dot_two_comma, - .array_init_dot, - .array_init_dot_comma, - .array_init, - .array_init_comma, - => { - var buffer: [2]Ast.Node.Index = undefined; - const array_init: Ast.full.ArrayInit = tree.fullArrayInit(&buffer, node).?; - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, array_init.ast.type_expr, range }); - for (array_init.ast.elements) |elem| { - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, elem, range }); - } - }, - - .struct_init_one, - .struct_init_one_comma, - .struct_init_dot_two, - .struct_init_dot_two_comma, - .struct_init_dot, - .struct_init_dot_comma, - .struct_init, - .struct_init_comma, - => { - var buffer: [2]Ast.Node.Index = undefined; - const struct_init: Ast.full.StructInit = tree.fullStructInit(&buffer, node).?; - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, struct_init.ast.type_expr, range }); - - for (struct_init.ast.fields) |field_init| { - if (struct_init.ast.fields.len > inlay_hints_max_inline_children) { - if (!isNodeInRange(tree, field_init, range)) continue; - } - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, field_init, range }); - } - }, - - .@"switch", - .switch_comma, - => { - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].lhs, range }); - - const extra = tree.extraData(node_data[node].rhs, Ast.Node.SubRange); - const cases = tree.extra_data[extra.start..extra.end]; - - for (cases) |case_node| { - if (cases.len > inlay_hints_max_inline_children) { - if (!isNodeInRange(tree, case_node, range)) continue; - } - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, case_node, range }); - } - }, - - .switch_case_one, - .switch_case, - .switch_case_inline_one, - .switch_case_inline, - => { - const switch_case = tree.fullSwitchCase(node).?; - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, switch_case.ast.target_expr, range }); - }, - - .while_simple, - .while_cont, - .@"while", - .for_simple, - .@"for", - => { - const while_node = ast.fullWhile(tree, node).?; - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, while_node.ast.cond_expr, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, while_node.ast.cont_expr, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, while_node.ast.then_expr, range }); - - if (while_node.ast.else_expr != 0) { - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, while_node.ast.else_expr, range }); - } - }, - - .if_simple, - .@"if", - => { - const if_node = ast.fullIf(tree, node).?; - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, if_node.ast.cond_expr, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, if_node.ast.then_expr, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, if_node.ast.else_expr, range }); - }, - - .fn_proto_simple, - .fn_proto_multi, - .fn_proto_one, - .fn_proto, - .fn_decl, - => { - var buffer: [1]Ast.Node.Index = undefined; - const fn_proto: Ast.full.FnProto = tree.fullFnProto(&buffer, node).?; - - var it = fn_proto.iterate(&tree); - while (ast.nextFnParam(&it)) |param_decl| { - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, param_decl.type_expr, range }); - } - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, fn_proto.ast.align_expr, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, fn_proto.ast.addrspace_expr, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, fn_proto.ast.section_expr, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, fn_proto.ast.callconv_expr, range }); - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, fn_proto.ast.return_type, range }); - - if (tag == .fn_decl) { - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].rhs, range }); - } - }, - - .container_decl, - .container_decl_trailing, - .container_decl_two, - .container_decl_two_trailing, - .container_decl_arg, - .container_decl_arg_trailing, - .tagged_union, - .tagged_union_trailing, - .tagged_union_two, - .tagged_union_two_trailing, - .tagged_union_enum_tag, - .tagged_union_enum_tag_trailing, - => { - var buffer: [2]Ast.Node.Index = undefined; - const decl: Ast.full.ContainerDecl = tree.fullContainerDecl(&buffer, node).?; - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, decl.ast.arg, range }); - - for (decl.ast.members) |child| { - if (decl.ast.members.len > inlay_hints_max_inline_children) { - if (!isNodeInRange(tree, child, range)) continue; - } - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, child, range }); - } - }, - - .container_field_init, - .container_field_align, - .container_field, - => { - const container_field = tree.fullContainerField(node).?; - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, container_field.ast.value_expr, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, container_field.ast.align_expr, range }); - }, - - .block_two, - .block_two_semicolon, - => { - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].lhs, range }); - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].rhs, range }); - }, - - .block, - .block_semicolon, - => { - const subrange = tree.extra_data[node_data[node].lhs..node_data[node].rhs]; - - for (subrange) |child| { - if (subrange.len > inlay_hints_max_inline_children) { - if (!isNodeInRange(tree, child, range)) continue; - } - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, child, range }); - } - }, - - .asm_simple, - .@"asm", - .asm_output, - .asm_input, - => { - const asm_node: Ast.full.Asm = tree.fullAsm(node) orelse return; - - try callWriteNodeInlayHint(allocator, .{ builder, arena, store, asm_node.ast.template, range }); - }, - - .error_value => {}, + else => {}, } } /// creates a list of `InlayHint`'s from the given document /// only parameter hints are created -/// only hints in the given range are created +/// only hints in the given loc are created pub fn writeRangeInlayHint( arena: *std.heap.ArenaAllocator, config: Config, store: *DocumentStore, handle: *const DocumentStore.Handle, - range: types.Range, + loc: offsets.Loc, hover_kind: types.MarkupKind, - encoding: offsets.Encoding, -) error{OutOfMemory}![]types.InlayHint { +) error{OutOfMemory}![]InlayHint { var builder: Builder = .{ - .arena = arena.allocator(), + .arena = arena, + .store = store, .config = &config, .handle = handle, .hints = .{}, .hover_kind = hover_kind, - .encoding = encoding, }; - for (handle.tree.rootDecls()) |child| { - if (!isNodeInRange(handle.tree, child, range)) continue; - try writeNodeInlayHint(&builder, arena, store, child, range); + const nodes = try ast.nodesAtLoc(arena.allocator(), handle.tree, loc); + + for (nodes) |child| { + try writeNodeInlayHint(&builder, child); + try ast.iterateChildrenRecursive(handle.tree, child, &builder, error{OutOfMemory}, writeNodeInlayHint); } - return builder.toOwnedSlice(); + return try builder.toOwnedSlice(); } diff --git a/src/offsets.zig b/src/offsets.zig index 7f328c4..38b8006 100644 --- a/src/offsets.zig +++ b/src/offsets.zig @@ -240,6 +240,29 @@ pub fn convertRangeEncoding(text: []const u8, range: types.Range, from_encoding: }; } +// returns true if a and b intersect +pub fn locIntersect(a: Loc, b: Loc) bool { + std.debug.assert(a.start <= a.end and b.start <= b.end); + const a_start_in_b = b.start <= a.start and a.start <= b.end; + const a_end_in_b = b.start <= a.end and a.end <= b.end; + return a_start_in_b or a_end_in_b; +} + +// returns true if a is inside b +pub fn locInside(inner: Loc, outer: Loc) bool { + std.debug.assert(inner.start <= inner.end and outer.start <= outer.end); + return outer.start <= inner.start and inner.end <= outer.end; +} + +// returns the union of a and b +pub fn locMerge(a: Loc, b: Loc) Loc { + std.debug.assert(a.start <= a.end and b.start <= b.end); + return .{ + .start = @min(a.start, b.start), + .end = @max(a.end, b.end), + }; +} + // Helper functions /// advance `position` which starts at `from_index` to `to_index` accounting for line breaks diff --git a/src/zls.zig b/src/zls.zig index fb362d0..cdd9b8a 100644 --- a/src/zls.zig +++ b/src/zls.zig @@ -1,6 +1,7 @@ // Used by tests as a package, can be used by tools such as // zigbot9001 to take advantage of zls' tools +pub const ast = @import("ast.zig"); pub const analysis = @import("analysis.zig"); pub const Header = @import("Header.zig"); pub const debug = @import("debug.zig"); diff --git a/tests/tests.zig b/tests/tests.zig index f9fb035..c968928 100644 --- a/tests/tests.zig +++ b/tests/tests.zig @@ -1,6 +1,7 @@ comptime { _ = @import("helper.zig"); + _ = @import("utility/ast.zig"); _ = @import("utility/offsets.zig"); _ = @import("utility/position_context.zig"); _ = @import("utility/uri.zig"); diff --git a/tests/utility/ast.zig b/tests/utility/ast.zig new file mode 100644 index 0000000..3a3f404 --- /dev/null +++ b/tests/utility/ast.zig @@ -0,0 +1,76 @@ +const std = @import("std"); +const zls = @import("zls"); + +const helper = @import("../helper.zig"); +const Context = @import("../context.zig").Context; +const ErrorBuilder = @import("../ErrorBuilder.zig"); + +const types = zls.types; +const offsets = zls.offsets; +const ast = zls.ast; + +const allocator = std.testing.allocator; + +test "nodesAtLoc" { + try testNodesAtLoc( + \\const foo = 5; + ); + try testNodesAtLoc( + \\const foo = 5; + \\var bar = 2; + ); + try testNodesAtLoc( + \\const foo = 5 + 2; + ); + try testNodesAtLoc( + \\fn foo(alpha: u32) void {} + \\const _ = foo(5); + ); +} + +fn testNodesAtLoc(source: []const u8) !void { + var ccp = try helper.collectClearPlaceholders(allocator, source); + defer ccp.deinit(allocator); + + const old_locs = ccp.locations.items(.old); + const locs = ccp.locations.items(.new); + + std.debug.assert(ccp.locations.len == 4); + std.debug.assert(std.mem.eql(u8, offsets.locToSlice(source, old_locs[0]), "")); + std.debug.assert(std.mem.eql(u8, offsets.locToSlice(source, old_locs[1]), "")); + std.debug.assert(std.mem.eql(u8, offsets.locToSlice(source, old_locs[2]), "")); + std.debug.assert(std.mem.eql(u8, offsets.locToSlice(source, old_locs[3]), "")); + + const inner_loc = offsets.Loc{ .start = locs[1].start, .end = locs[2].start }; + const outer_loc = offsets.Loc{ .start = locs[0].start, .end = locs[3].end }; + + const new_source = try allocator.dupeZ(u8, ccp.new_source); + defer allocator.free(new_source); + + var tree = try std.zig.parse(allocator, new_source); + defer tree.deinit(allocator); + + const nodes = try ast.nodesAtLoc(allocator, tree, inner_loc); + defer allocator.free(nodes); + + const actual_loc = offsets.Loc{ + .start = offsets.nodeToLoc(tree, nodes[0]).start, + .end = offsets.nodeToLoc(tree, nodes[nodes.len - 1]).end, + }; + + var error_builder = ErrorBuilder.init(allocator, new_source); + defer error_builder.deinit(); + errdefer error_builder.writeDebug(); + + if (outer_loc.start != actual_loc.start) { + try error_builder.msgAtIndex("actual start here", actual_loc.start, .err, .{}); + try error_builder.msgAtIndex("expected start here", outer_loc.start, .err, .{}); + return error.LocStartMismatch; + } + + if (outer_loc.end != actual_loc.end) { + try error_builder.msgAtIndex("actual end here", actual_loc.end, .err, .{}); + try error_builder.msgAtIndex("expected end here", outer_loc.end, .err, .{}); + return error.LocEndMismatch; + } +} From 767cf7a52d9f294fb983ff37015ef167ef05cf1f Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Wed, 1 Feb 2023 13:11:45 +0100 Subject: [PATCH 08/12] fix iterateChildren on if (#951) --- src/ast.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast.zig b/src/ast.zig index 8caa210..bf7658e 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -1406,7 +1406,7 @@ pub fn iterateChildren( .@"if", .if_simple, => { - const if_ast = ifFull(tree, node).ast; + const if_ast = fullIf(tree, node).?.ast; try callback(context, if_ast.cond_expr); try callback(context, if_ast.then_expr); try callback(context, if_ast.else_expr); From 1b3c3defb74cba5fc5e044b5d3c48ab1ab6ce453 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Thu, 2 Feb 2023 00:29:36 +0100 Subject: [PATCH 09/12] rewrite folding range (#954) --- src/Server.zig | 245 +-------------------- src/ast.zig | 26 --- src/folding_range.zig | 311 +++++++++++++++++++++++++++ tests/lsp_features/folding_range.zig | 217 ++++++++++++++++--- 4 files changed, 504 insertions(+), 295 deletions(-) create mode 100644 src/folding_range.zig diff --git a/src/Server.zig b/src/Server.zig index f6dba9f..b67f26e 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -14,6 +14,7 @@ const offsets = @import("offsets.zig"); const semantic_tokens = @import("semantic_tokens.zig"); const inlay_hints = @import("inlay_hints.zig"); const code_actions = @import("code_actions.zig"); +const folding_range = @import("folding_range.zig"); const shared = @import("shared.zig"); const Ast = std.zig.Ast; const tracy = @import("tracy.zig"); @@ -2579,250 +2580,10 @@ fn foldingRangeHandler(server: *Server, request: types.FoldingRangeParams) Error const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); - const Token = std.zig.Token; - const Node = Ast.Node; - const allocator = server.arena.allocator(); const handle = server.document_store.getHandle(request.textDocument.uri) orelse return null; + const allocator = server.arena.allocator(); - const helper = struct { - const Inclusivity = enum { inclusive, exclusive }; - - fn addTokRange( - p_ranges: *std.ArrayList(types.FoldingRange), - tree: Ast, - start: Ast.TokenIndex, - end: Ast.TokenIndex, - end_reach: Inclusivity, - ) std.mem.Allocator.Error!void { - if (tree.tokensOnSameLine(start, end)) return; - std.debug.assert(start <= end); - - const start_index = offsets.tokenToIndex(tree, start); - const end_index = offsets.tokenToIndex(tree, end); - - const start_line = std.mem.count(u8, tree.source[0..start_index], "\n"); - const end_line = start_line + std.mem.count(u8, tree.source[start_index..end_index], "\n"); - - try p_ranges.append(.{ - .startLine = @intCast(u32, start_line), - .endLine = @intCast(u32, end_line) - @boolToInt(end_reach == .exclusive), - }); - } - }; - - // Used to store the result - var ranges = std.ArrayList(types.FoldingRange).init(allocator); - - const token_tags: []const Token.Tag = handle.tree.tokens.items(.tag); - const node_tags: []const Node.Tag = handle.tree.nodes.items(.tag); - - if (token_tags.len == 0) return null; - if (token_tags[0] == .container_doc_comment) { - var tok: Ast.TokenIndex = 1; - while (tok < token_tags.len) : (tok += 1) { - if (token_tags[tok] != .container_doc_comment) { - break; - } - } - if (tok > 1) { // each container doc comment has its own line, so each one counts for a line - try ranges.append(.{ - .startLine = 0, - .endLine = tok - 1, - }); - } - } - - for (node_tags) |node_tag, i| { - const node = @intCast(Node.Index, i); - - switch (node_tag) { - .root => continue, - // only fold the expression pertaining to the if statement, and the else statement, each respectively. - // TODO: Should folding multiline condition expressions also be supported? Ditto for the other control flow structures. - .@"if", - .if_simple, - => { - const if_full = ast.fullIf(handle.tree, node).?; - - const start_tok_1 = ast.lastToken(handle.tree, if_full.ast.cond_expr); - const end_tok_1 = ast.lastToken(handle.tree, if_full.ast.then_expr); - try helper.addTokRange(&ranges, handle.tree, start_tok_1, end_tok_1, .inclusive); - - if (if_full.ast.else_expr == 0) continue; - - const start_tok_2 = if_full.else_token; - const end_tok_2 = ast.lastToken(handle.tree, if_full.ast.else_expr); - - try helper.addTokRange(&ranges, handle.tree, start_tok_2, end_tok_2, .inclusive); - }, - - // same as if/else - .@"for", - .for_simple, - .@"while", - .while_cont, - .while_simple, - => { - const loop_full = ast.fullWhile(handle.tree, node).?; - - const start_tok_1 = ast.lastToken(handle.tree, loop_full.ast.cond_expr); - const end_tok_1 = ast.lastToken(handle.tree, loop_full.ast.then_expr); - try helper.addTokRange(&ranges, handle.tree, start_tok_1, end_tok_1, .inclusive); - - if (loop_full.ast.else_expr == 0) continue; - - const start_tok_2 = loop_full.else_token; - const end_tok_2 = ast.lastToken(handle.tree, loop_full.ast.else_expr); - try helper.addTokRange(&ranges, handle.tree, start_tok_2, end_tok_2, .inclusive); - }, - - .global_var_decl, - .simple_var_decl, - .aligned_var_decl, - .container_field_init, - .container_field_align, - .container_field, - .fn_proto, - .fn_proto_multi, - .fn_proto_one, - .fn_proto_simple, - .fn_decl, - => decl_node_blk: { - doc_comment_range: { - const first_tok: Ast.TokenIndex = handle.tree.firstToken(node); - if (first_tok == 0) break :doc_comment_range; - - const end_doc_tok = first_tok - 1; - if (token_tags[end_doc_tok] != .doc_comment) break :doc_comment_range; - - var start_doc_tok = end_doc_tok; - while (start_doc_tok != 0) { - if (token_tags[start_doc_tok - 1] != .doc_comment) break; - start_doc_tok -= 1; - } - - try helper.addTokRange(&ranges, handle.tree, start_doc_tok, end_doc_tok, .inclusive); - } - - // Function prototype folding regions - var buffer: [1]Node.Index = undefined; - const fn_proto = handle.tree.fullFnProto(&buffer, node) orelse - break :decl_node_blk; - - const list_start_tok: Ast.TokenIndex = fn_proto.lparen; - const list_end_tok: Ast.TokenIndex = ast.lastToken(handle.tree, fn_proto.ast.proto_node); - - if (handle.tree.tokensOnSameLine(list_start_tok, list_end_tok)) break :decl_node_blk; - try helper.addTokRange(&ranges, handle.tree, list_start_tok, list_end_tok, .exclusive); - - var it = fn_proto.iterate(&handle.tree); - while (ast.nextFnParam(&it)) |param| { - const doc_start_tok = param.first_doc_comment orelse continue; - var doc_end_tok = doc_start_tok; - - while (token_tags[doc_end_tok + 1] == .doc_comment) - doc_end_tok += 1; - - try helper.addTokRange(&ranges, handle.tree, doc_start_tok, doc_end_tok, .inclusive); - } - }, - - .@"catch", - .@"orelse", - .multiline_string_literal, - // TODO: Similar to condition expressions in control flow structures, should folding multiline grouped expressions be enabled? - // .grouped_expression, - => { - const start_tok = handle.tree.firstToken(node); - const end_tok = ast.lastToken(handle.tree, node); - try helper.addTokRange(&ranges, handle.tree, start_tok, end_tok, .inclusive); - }, - - // most other trivial cases can go through here. - else => { - switch (node_tag) { - .array_init, - .array_init_one, - .array_init_dot_two, - .array_init_one_comma, - .array_init_dot_two_comma, - .array_init_dot, - .array_init_dot_comma, - .array_init_comma, - - .struct_init, - .struct_init_one, - .struct_init_one_comma, - .struct_init_dot_two, - .struct_init_dot_two_comma, - .struct_init_dot, - .struct_init_dot_comma, - .struct_init_comma, - - .@"switch", - .switch_comma, - => {}, - - else => disallow_fold: { - if (ast.isBlock(handle.tree, node)) - break :disallow_fold; - - if (ast.isCall(handle.tree, node)) - break :disallow_fold; - - if (ast.isBuiltinCall(handle.tree, node)) - break :disallow_fold; - - if (ast.isContainer(handle.tree, node) and node_tag != .root) - break :disallow_fold; - - continue; // no conditions met, continue iterating without adding this potential folding range - }, - } - - const start_tok = handle.tree.firstToken(node); - const end_tok = ast.lastToken(handle.tree, node); - try helper.addTokRange(&ranges, handle.tree, start_tok, end_tok, .exclusive); - }, - } - } - - // Iterate over the source code and look for code regions with #region #endregion - { - // We add opened folding regions to a stack as we go and pop one off when we find a closing brace. - // As an optimization we start with a reasonable capacity, which should work well in most cases since - // people will almost never have nesting that deep. - var stack = try std.ArrayList(u32).initCapacity(allocator, 10); - - var i: usize = 0; - var lines_count: u32 = 0; - while (i < handle.tree.source.len) : (i += 1) { - const slice = handle.tree.source[i..]; - - if (slice[0] == '\n') { - lines_count += 1; - } - - if (std.mem.startsWith(u8, slice, "//#region")) { - try stack.append(lines_count); - } - - if (std.mem.startsWith(u8, slice, "//#endregion") and stack.items.len > 0) { - const start_line = stack.pop(); - const end_line = lines_count; - - // Add brace pairs but discard those from the same line, no need to waste memory on them - if (start_line != end_line) { - try ranges.append(.{ - .startLine = start_line, - .endLine = end_line, - }); - } - } - } - } - - return ranges.items; + return try folding_range.generateFoldingRanges(allocator, handle.tree, server.offset_encoding); } pub const SelectionRange = struct { diff --git a/src/ast.zig b/src/ast.zig index bf7658e..cac2502 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -976,32 +976,6 @@ pub fn isBuiltinCall(tree: Ast, node: Ast.Node.Index) bool { }; } -pub fn isCall(tree: Ast, node: Ast.Node.Index) bool { - return switch (tree.nodes.items(.tag)[node]) { - .call, - .call_comma, - .call_one, - .call_one_comma, - .async_call, - .async_call_comma, - .async_call_one, - .async_call_one_comma, - => true, - else => false, - }; -} - -pub fn isBlock(tree: Ast, node: Ast.Node.Index) bool { - return switch (tree.nodes.items(.tag)[node]) { - .block_two, - .block_two_semicolon, - .block, - .block_semicolon, - => true, - else => false, - }; -} - /// returns a list of parameters pub fn builtinCallParams(tree: Ast, node: Ast.Node.Index, buf: *[2]Ast.Node.Index) ?[]const Node.Index { const node_data = tree.nodes.items(.data); diff --git a/src/folding_range.zig b/src/folding_range.zig new file mode 100644 index 0000000..a0a9729 --- /dev/null +++ b/src/folding_range.zig @@ -0,0 +1,311 @@ +const std = @import("std"); +const ast = @import("ast.zig"); +const types = @import("lsp.zig"); +const offsets = @import("offsets.zig"); +const Ast = std.zig.Ast; + +const FoldingRange = struct { + loc: offsets.Loc, + kind: ?types.FoldingRangeKind = null, +}; + +const Inclusivity = enum { inclusive, exclusive }; + +const Builder = struct { + allocator: std.mem.Allocator, + locations: std.ArrayListUnmanaged(FoldingRange), + tree: Ast, + encoding: offsets.Encoding, + + pub fn deinit(builder: *Builder) void { + builder.locations.deinit(builder.allocator); + } + + pub fn add( + builder: *Builder, + kind: ?types.FoldingRangeKind, + start: Ast.TokenIndex, + end: Ast.TokenIndex, + start_reach: Inclusivity, + end_reach: Inclusivity, + ) error{OutOfMemory}!void { + if (builder.tree.tokensOnSameLine(start, end)) return; + std.debug.assert(start <= end); + const start_loc = offsets.tokenToLoc(builder.tree, start); + const end_loc = offsets.tokenToLoc(builder.tree, end); + + try builder.locations.append(builder.allocator, .{ + .loc = .{ + .start = if (start_reach == .exclusive) start_loc.end else start_loc.start, + .end = if (end_reach == .exclusive) end_loc.start else end_loc.end, + }, + .kind = kind, + }); + } + + pub fn addNode( + builder: *Builder, + kind: ?types.FoldingRangeKind, + node: Ast.Node.Index, + start_reach: Inclusivity, + end_reach: Inclusivity, + ) error{OutOfMemory}!void { + try builder.add(kind, builder.tree.firstToken(node), ast.lastToken(builder.tree, node), start_reach, end_reach); + } + + pub fn getRanges(builder: Builder) error{OutOfMemory}![]types.FoldingRange { + var result = try builder.allocator.alloc(types.FoldingRange, builder.locations.items.len); + errdefer builder.allocator.free(result); + + for (result) |*r, i| { + r.* = .{ + .startLine = undefined, + .endLine = undefined, + .kind = builder.locations.items[i].kind, + }; + } + + const Item = struct { + output: *types.FoldingRange, + input: *const FoldingRange, + where: enum { start, end }, + + const Self = @This(); + + fn getInputIndex(self: Self) usize { + return switch (self.where) { + .start => self.input.loc.start, + .end => self.input.loc.end, + }; + } + + fn lessThan(_: void, lhs: Self, rhs: Self) bool { + return lhs.getInputIndex() < rhs.getInputIndex(); + } + }; + + // one item for every start and end position + var items = try builder.allocator.alloc(Item, builder.locations.items.len * 2); + defer builder.allocator.free(items); + + for (builder.locations.items) |*folding_range, i| { + items[2 * i + 0] = .{ .output = &result[i], .input = folding_range, .where = .start }; + items[2 * i + 1] = .{ .output = &result[i], .input = folding_range, .where = .end }; + } + + // sort items based on their source position + std.sort.sort(Item, items, {}, Item.lessThan); + + var last_index: usize = 0; + var last_position: types.Position = .{ .line = 0, .character = 0 }; + for (items) |item| { + const index = item.getInputIndex(); + const position = offsets.advancePosition(builder.tree.source, last_position, last_index, index, builder.encoding); + defer last_index = index; + defer last_position = position; + + switch (item.where) { + .start => { + item.output.startLine = position.line; + item.output.startCharacter = position.character; + }, + .end => { + item.output.endLine = position.line; + item.output.endCharacter = position.character; + }, + } + } + + return result; + } +}; + +pub fn generateFoldingRanges(allocator: std.mem.Allocator, tree: Ast, encoding: offsets.Encoding) error{OutOfMemory}![]types.FoldingRange { + var builder = Builder{ + .allocator = allocator, + .locations = .{}, + .tree = tree, + .encoding = encoding, + }; + defer builder.deinit(); + + const token_tags = tree.tokens.items(.tag); + const node_tags = tree.nodes.items(.tag); + const main_tokens = tree.nodes.items(.main_token); + + var start_doc_comment: ?Ast.TokenIndex = null; + var end_doc_comment: ?Ast.TokenIndex = null; + for (token_tags) |tag, i| { + const token = @intCast(Ast.TokenIndex, i); + switch (tag) { + .doc_comment, + .container_doc_comment, + => { + if (start_doc_comment == null) { + start_doc_comment = token; + end_doc_comment = token; + } else { + end_doc_comment = token; + } + }, + else => { + if (start_doc_comment != null and end_doc_comment != null) { + try builder.add(.comment, start_doc_comment.?, end_doc_comment.?, .inclusive, .inclusive); + start_doc_comment = null; + end_doc_comment = null; + } + }, + } + } + + // TODO add folding range normal comments + + // TODO add folding range for top level `@Import()` + + for (node_tags) |node_tag, i| { + const node = @intCast(Ast.Node.Index, i); + + switch (node_tag) { + .root => continue, + // TODO: Should folding multiline condition expressions also be supported? Ditto for the other control flow structures. + + .fn_proto, + .fn_proto_multi, + .fn_proto_one, + .fn_proto_simple, + // .fn_decl + => { + var buffer: [1]Ast.Node.Index = undefined; + const fn_proto = tree.fullFnProto(&buffer, node).?; + + const list_start_tok = fn_proto.lparen; + const list_end_tok = ast.lastToken(tree, node) -| 1; + + try builder.add(null, list_start_tok, list_end_tok, .exclusive, .exclusive); + }, + + .block_two, + .block_two_semicolon, + .block, + .block_semicolon, + => { + try builder.addNode(null, node, .exclusive, .exclusive); + }, + .@"switch", + .switch_comma, + => { + const lhs = tree.nodes.items(.data)[node].lhs; + const start_tok = ast.lastToken(tree, lhs) + 2; // lparen + rbrace + const end_tok = ast.lastToken(tree, node); + try builder.add(null, start_tok, end_tok, .exclusive, .exclusive); + }, + + .switch_case_one, + .switch_case_inline_one, + .switch_case, + .switch_case_inline, + => { + const switch_case = tree.fullSwitchCase(node).?.ast; + if (switch_case.values.len >= 4) { + const first_value = tree.firstToken(switch_case.values[0]); + const last_value = ast.lastToken(tree, switch_case.values[switch_case.values.len - 1]); + try builder.add(null, first_value, last_value, .inclusive, .inclusive); + } + }, + + .container_decl, + .container_decl_trailing, + .container_decl_arg, + .container_decl_arg_trailing, + .container_decl_two, + .container_decl_two_trailing, + .tagged_union, + .tagged_union_trailing, + .tagged_union_two, + .tagged_union_two_trailing, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + => { + var buffer: [2]Ast.Node.Index = undefined; + const container_decl = tree.fullContainerDecl(&buffer, node).?; + if (container_decl.ast.members.len != 0) { + const first_member = container_decl.ast.members[0]; + const start_tok = tree.firstToken(first_member) -| 1; + const end_tok = ast.lastToken(tree, node); + try builder.add(null, start_tok, end_tok, .exclusive, .exclusive); + } + }, + + .call, + .call_comma, + .call_one, + .call_one_comma, + .async_call, + .async_call_comma, + .async_call_one, + .async_call_one_comma, + => { + const lparen = main_tokens[node]; + try builder.add(null, lparen, ast.lastToken(tree, node), .exclusive, .exclusive); + }, + + // everything after here is mostly untested + .array_init, + .array_init_one, + .array_init_dot_two, + .array_init_one_comma, + .array_init_dot_two_comma, + .array_init_dot, + .array_init_dot_comma, + .array_init_comma, + + .struct_init, + .struct_init_one, + .struct_init_one_comma, + .struct_init_dot_two, + .struct_init_dot_two_comma, + .struct_init_dot, + .struct_init_dot_comma, + .struct_init_comma, + + .builtin_call, + .builtin_call_comma, + .builtin_call_two, + .builtin_call_two_comma, + + .multiline_string_literal, + .error_set_decl, + .test_decl, + => { + try builder.addNode(null, node, .inclusive, .inclusive); + }, + + else => {}, + } + } + + // We add opened folding regions to a stack as we go and pop one off when we find a closing brace. + var stack = std.ArrayListUnmanaged(usize){}; + + var i: usize = 0; + while (std.mem.indexOfPos(u8, tree.source, i, "//#")) |possible_region| { + defer i = possible_region + "//#".len; + if (std.mem.startsWith(u8, tree.source[possible_region..], "//#region")) { + try stack.append(allocator, possible_region); + } else if (std.mem.startsWith(u8, tree.source[possible_region..], "//#endregion")) { + const start_index = stack.popOrNull() orelse break; // null means there are more endregions than regions + const end_index = offsets.lineLocAtIndex(tree.source, possible_region).end; + const is_same_line = std.mem.indexOfScalar(u8, tree.source[start_index..end_index], '\n') == null; + if (is_same_line) continue; + try builder.locations.append(allocator, .{ + .loc = .{ + .start = start_index, + .end = end_index, + }, + .kind = .region, + }); + } + } + + return try builder.getRanges(); +} diff --git a/tests/lsp_features/folding_range.zig b/tests/lsp_features/folding_range.zig index 472ed4d..2e99742 100644 --- a/tests/lsp_features/folding_range.zig +++ b/tests/lsp_features/folding_range.zig @@ -11,34 +11,197 @@ const types = zls.types; const allocator: std.mem.Allocator = std.testing.allocator; test "foldingRange - empty" { - try testFoldingRange("", "[]"); + try testFoldingRange("", &.{}); } -test "foldingRange - smoke" { +test "foldingRange - doc comment" { + try testFoldingRange( + \\/// hello + \\/// world + \\var foo = 5; + , &.{ + .{ .startLine = 0, .startCharacter = 0, .endLine = 1, .endCharacter = 9, .kind = .comment }, + }); +} + +test "foldingRange - region" { + try testFoldingRange( + \\const foo = 0; + \\//#region + \\const bar = 1; + \\//#endregion + \\const baz = 2; + , &.{ + .{ .startLine = 1, .startCharacter = 0, .endLine = 3, .endCharacter = 12, .kind = .region }, + }); + try testFoldingRange( + \\//#region + \\const foo = 0; + \\//#region + \\const bar = 1; + \\//#endregion + \\const baz = 2; + \\//#endregion + , &.{ + .{ .startLine = 2, .startCharacter = 0, .endLine = 4, .endCharacter = 12, .kind = .region }, + .{ .startLine = 0, .startCharacter = 0, .endLine = 6, .endCharacter = 12, .kind = .region }, + }); +} + +test "foldingRange - if" { + try testFoldingRange( + \\const foo = if (false) { + \\ + \\}; + , &.{ + .{ .startLine = 0, .startCharacter = 24, .endLine = 2, .endCharacter = 0 }, + }); + try testFoldingRange( + \\const foo = if (false) { + \\ + \\} else { + \\ + \\}; + , &.{ + .{ .startLine = 0, .startCharacter = 24, .endLine = 2, .endCharacter = 0 }, + .{ .startLine = 2, .startCharacter = 8, .endLine = 4, .endCharacter = 0 }, + }); +} + +test "foldingRange - for/while" { + try testFoldingRange( + \\const foo = for ("") |_| { + \\ + \\}; + , &.{ + .{ .startLine = 0, .startCharacter = 26, .endLine = 2, .endCharacter = 0 }, + }); + try testFoldingRange( + \\const foo = while (true) { + \\ + \\}; + , &.{ + .{ .startLine = 0, .startCharacter = 26, .endLine = 2, .endCharacter = 0 }, + }); +} + +test "foldingRange - switch" { + try testFoldingRange( + \\const foo = switch (5) { + \\ 0 => {}, + \\ 1 => {} + \\}; + , &.{ + .{ .startLine = 0, .startCharacter = 24, .endLine = 3, .endCharacter = 0 }, + }); + try testFoldingRange( + \\const foo = switch (5) { + \\ 0 => {}, + \\ 1 => {}, + \\}; + , &.{ + .{ .startLine = 0, .startCharacter = 24, .endLine = 3, .endCharacter = 0 }, + }); +} + +test "foldingRange - function" { try testFoldingRange( \\fn main() u32 { \\ return 1 + 1; \\} - , - \\[{"startLine":0,"endLine":1}] - ); -} - -test "foldingRange - #801" { + , &.{ + .{ .startLine = 0, .startCharacter = 15, .endLine = 2, .endCharacter = 0 }, + }); try testFoldingRange( - \\fn score(c: u8) !u32 { - \\ return switch(c) { - \\ 'a'...'z' => c - 'a', - \\ 'A'...'Z' => c - 'A', - \\ _ => error - \\ }; + \\fn main( + \\ a: ?u32, + \\) u32 { + \\ return 1 + 1; \\} - , - \\[{"startLine":1,"endLine":4},{"startLine":0,"endLine":5}] - ); + , &.{ + .{ .startLine = 0, .startCharacter = 8, .endLine = 2, .endCharacter = 0 }, + .{ .startLine = 2, .startCharacter = 7, .endLine = 4, .endCharacter = 0 }, + }); } -fn testFoldingRange(source: []const u8, expect: []const u8) !void { +test "foldingRange - function with doc comment" { + try testFoldingRange( + \\/// this is + \\/// a function + \\fn foo( + \\ /// this is a parameter + \\ a: u32, + \\ /// + \\ /// this is another parameter + \\ b: u32, + \\) void {} + , &.{ + .{ .startLine = 0, .startCharacter = 0, .endLine = 1, .endCharacter = 14, .kind = .comment }, + .{ .startLine = 5, .startCharacter = 4, .endLine = 6, .endCharacter = 33, .kind = .comment }, + .{ .startLine = 2, .startCharacter = 7, .endLine = 8, .endCharacter = 0 }, + }); +} + +test "foldingRange - container decl" { + try testFoldingRange( + \\const Foo = struct { + \\ alpha: u32, + \\ beta: []const u8, + \\}; + , &.{ + .{ .startLine = 0, .startCharacter = 20, .endLine = 3, .endCharacter = 0 }, + }); + try testFoldingRange( + \\const Foo = packed struct(u32) { + \\ alpha: u16, + \\ beta: u16, + \\}; + , &.{ + // .{ .startLine = 0, .startCharacter = 32, .endLine = 3, .endCharacter = 0 }, // TODO + .{ .startLine = 0, .startCharacter = 32, .endLine = 2, .endCharacter = 11 }, + }); + try testFoldingRange( + \\const Foo = union { + \\ alpha: u32, + \\ beta: []const u8, + \\}; + , &.{ + .{ .startLine = 0, .startCharacter = 19, .endLine = 3, .endCharacter = 0 }, + }); + try testFoldingRange( + \\const Foo = union(enum) { + \\ alpha: u32, + \\ beta: []const u8, + \\}; + , &.{ + .{ .startLine = 0, .startCharacter = 25, .endLine = 3, .endCharacter = 0 }, + }); +} + +test "foldingRange - call" { + try testFoldingRange( + \\extern fn foo(a: bool, b: ?usize) void; + \\const result = foo( + \\ false, + \\ null, + \\); + , &.{ + .{ .startLine = 1, .startCharacter = 19, .endLine = 4, .endCharacter = 0 }, + }); +} + +test "foldingRange - multi-line string literal" { + try testFoldingRange( + \\const foo = + \\ \\hello + \\ \\world + \\; + , &.{ + .{ .startLine = 1, .startCharacter = 4, .endLine = 3, .endCharacter = 0 }, + }); +} + +fn testFoldingRange(source: []const u8, expect: []const types.FoldingRange) !void { var ctx = try Context.init(); defer ctx.deinit(); @@ -53,16 +216,16 @@ fn testFoldingRange(source: []const u8, expect: []const u8) !void { const response = try ctx.requestGetResponse(?[]types.FoldingRange, "textDocument/foldingRange", params); - var actual = std.ArrayList(u8).init(allocator); - defer actual.deinit(); + var actual = std.ArrayListUnmanaged(u8){}; + defer actual.deinit(allocator); - try tres.stringify(response.result, .{ - .emit_null_optional_fields = false, - }, actual.writer()); - try expectEqualJson(expect, actual.items); -} + var expected = std.ArrayListUnmanaged(u8){}; + defer expected.deinit(allocator); + + const options = std.json.StringifyOptions{ .emit_null_optional_fields = false, .whitespace = .{ .indent = .None } }; + try tres.stringify(response.result, options, actual.writer(allocator)); + try tres.stringify(expect, options, expected.writer(allocator)); -fn expectEqualJson(expect: []const u8, actual: []const u8) !void { // TODO: Actually compare strings as JSON values. - return std.testing.expectEqualStrings(expect, actual); + try std.testing.expectEqualStrings(expected.items, actual.items); } From 5ec4770ac2df24863b25ce340bbe8ad658fa4b6b Mon Sep 17 00:00:00 2001 From: Auguste Rame <19855629+SuperAuguste@users.noreply.github.com> Date: Thu, 2 Feb 2023 23:07:49 -0500 Subject: [PATCH 10/12] Fix CI checkout origin (#959) --- .github/workflows/fuzz.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 07eae8a..0565953 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -67,6 +67,8 @@ jobs: path: zls fetch-depth: 0 submodules: true + ref: ${{ github.event.pull_request.head.sha || github.sha }} + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - name: Build zls run: | From 75f0617279c54288310633d44f0324c80110305f Mon Sep 17 00:00:00 2001 From: nullptrdevs <16590917+nullptrdevs@users.noreply.github.com> Date: Thu, 2 Feb 2023 20:38:09 -0800 Subject: [PATCH 11/12] Skip incomplete fn_protos when generating folding ranges (#958) --- src/folding_range.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/folding_range.zig b/src/folding_range.zig index a0a9729..bfd7a0b 100644 --- a/src/folding_range.zig +++ b/src/folding_range.zig @@ -181,6 +181,8 @@ pub fn generateFoldingRanges(allocator: std.mem.Allocator, tree: Ast, encoding: const list_start_tok = fn_proto.lparen; const list_end_tok = ast.lastToken(tree, node) -| 1; + if (list_start_tok > list_end_tok) continue; // Incomplete, ie `fn a()` + try builder.add(null, list_start_tok, list_end_tok, .exclusive, .exclusive); }, From 6297536d7bfef61096e4f323da4777ec4ef8d70a Mon Sep 17 00:00:00 2001 From: nullptrdevs <16590917+nullptrdevs@users.noreply.github.com> Date: Fri, 3 Feb 2023 14:06:57 -0800 Subject: [PATCH 12/12] Work in Zig's breaking changes (build sys apis) (#955) --- build.zig | 67 +++++++++++++++++++++++++----------- src/special/build_runner.zig | 27 +++++++++++---- 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/build.zig b/build.zig index 9010aa7..8d6da57 100644 --- a/build.zig +++ b/build.zig @@ -7,15 +7,22 @@ const zls_version = std.builtin.Version{ .major = 0, .minor = 11, .patch = 0 }; pub fn build(b: *std.build.Builder) !void { comptime { const current_zig = builtin.zig_version; - const min_zig = std.SemanticVersion.parse("0.11.0-dev.1254+1f8f79cd5") catch return; // add helper functions to std.zig.Ast + const min_zig = std.SemanticVersion.parse("0.11.0-dev.1524+efa25e7d5") catch return; // build API changes if (current_zig.order(min_zig) == .lt) { @compileError(std.fmt.comptimePrint("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); } } - const target = b.standardTargetOptions(.{}); - const mode = b.standardReleaseOptions(); - const exe = b.addExecutable("zls", "src/main.zig"); + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "zls", + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + const exe_options = b.addOptions(); exe.addOptions("build_options", exe_options); @@ -105,11 +112,17 @@ pub fn build(b: *std.build.Builder) !void { const KNOWN_FOLDERS_DEFAULT_PATH = "src/known-folders/known-folders.zig"; const known_folders_path = b.option([]const u8, "known-folders", "Path to known-folders package (default: " ++ KNOWN_FOLDERS_DEFAULT_PATH ++ ")") orelse KNOWN_FOLDERS_DEFAULT_PATH; - exe.addPackage(.{ .name = "known-folders", .source = .{ .path = known_folders_path } }); + exe.addPackage(.{ + .name = "known-folders", + .source = .{ .path = known_folders_path }, + }); const TRES_DEFAULT_PATH = "src/tres/tres.zig"; const tres_path = b.option([]const u8, "tres", "Path to tres package (default: " ++ TRES_DEFAULT_PATH ++ ")") orelse TRES_DEFAULT_PATH; - exe.addPackage(.{ .name = "tres", .source = .{ .path = tres_path } }); + exe.addPackage(.{ + .name = "tres", + .source = .{ .path = tres_path }, + }); const check_submodules_step = CheckSubmodulesStep.init(b, &.{ known_folders_path, @@ -137,12 +150,16 @@ pub fn build(b: *std.build.Builder) !void { } } - exe.setTarget(target); - exe.setBuildMode(mode); exe.install(); - const gen_exe = b.addExecutable("zls_gen", "src/config_gen/config_gen.zig"); - gen_exe.addPackage(.{ .name = "tres", .source = .{ .path = tres_path } }); + const gen_exe = b.addExecutable(.{ + .name = "zls_gen", + .root_source_file = .{ .path = "src/config_gen/config_gen.zig" }, + }); + gen_exe.addPackage(.{ + .name = "tres", + .source = .{ .path = tres_path }, + }); const gen_cmd = gen_exe.run(); gen_cmd.addArgs(&.{ @@ -160,7 +177,12 @@ pub fn build(b: *std.build.Builder) !void { const test_step = b.step("test", "Run all the tests"); test_step.dependOn(b.getInstallStep()); - var tests = b.addTest("tests/tests.zig"); + var tests = b.addTest(.{ + .root_source_file = .{ .path = "tests/tests.zig" }, + .target = target, + .optimize = .Debug, + }); + tests.setFilter(b.option( []const u8, "test-filter", @@ -179,29 +201,34 @@ pub fn build(b: *std.build.Builder) !void { }); } - tests.addPackage(.{ .name = "zls", .source = .{ .path = "src/zls.zig" }, .dependencies = exe.packages.items }); - tests.addPackage(.{ .name = "tres", .source = .{ .path = tres_path } }); - tests.setBuildMode(.Debug); - tests.setTarget(target); + tests.addPackage(.{ + .name = "zls", + .source = .{ .path = "src/zls.zig" }, + .dependencies = exe.packages.items, + }); + tests.addPackage(.{ + .name = "tres", + .source = .{ .path = tres_path }, + }); test_step.dependOn(&tests.step); } const CheckSubmodulesStep = struct { - step: std.build.Step, - builder: *std.build.Builder, + step: std.Build.Step, + builder: *std.Build, submodules: []const []const u8, - pub fn init(builder: *std.build.Builder, submodules: []const []const u8) *CheckSubmodulesStep { + pub fn init(builder: *std.Build, submodules: []const []const u8) *CheckSubmodulesStep { var self = builder.allocator.create(CheckSubmodulesStep) catch unreachable; self.* = CheckSubmodulesStep{ .builder = builder, - .step = std.build.Step.init(.custom, "Check Submodules", builder.allocator, make), + .step = std.Build.Step.init(.custom, "Check Submodules", builder.allocator, make), .submodules = builder.allocator.dupe([]const u8, submodules) catch unreachable, }; return self; } - fn make(step: *std.build.Step) anyerror!void { + fn make(step: *std.Build.Step) anyerror!void { const self = @fieldParentPtr(CheckSubmodulesStep, "step", step); for (self.submodules) |path| { const access = std.fs.accessAbsolute(self.builder.pathFromRoot(path), .{}); diff --git a/src/special/build_runner.zig b/src/special/build_runner.zig index 5940d7c..dfdf97f 100644 --- a/src/special/build_runner.zig +++ b/src/special/build_runner.zig @@ -48,13 +48,26 @@ pub fn main() !void { return error.InvalidArgs; }; - const builder = try Builder.create( - allocator, - zig_exe, - build_root, - cache_root, - global_cache_root, - ); + const builder = blk: { + // Zig 0.11.0-dev.1524+ + if (@hasDecl(std, "Build")) { + const host = try std.zig.system.NativeTargetInfo.detect(.{}); + break :blk try Builder.create( + allocator, + zig_exe, + build_root, + cache_root, + global_cache_root, + host, + ); + } else break :blk try Builder.create( + allocator, + zig_exe, + build_root, + cache_root, + global_cache_root, + ); + }; defer builder.destroy();