Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve tuple literal code analysis #2079

Merged
merged 1 commit into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1108,9 +1108,18 @@ fn resolveBracketAccessType(analyser: *Analyser, lhs: Type, rhs: BracketAccessKi
}
}

fn resolveTupleFieldType(analyser: *Analyser, tuple: Type, index: usize) error{OutOfMemory}!?Type {
pub fn resolveTupleFieldType(analyser: *Analyser, tuple: Type, index: usize) error{OutOfMemory}!?Type {
const scope_handle = switch (tuple.data) {
.container => |s| s,
.other => |node| {
var buffer: [2]Ast.Node.Index = undefined;
const array_init_info = node.handle.tree.fullArrayInit(&buffer, node.node) orelse return null;

const elements = array_init_info.ast.elements;
if (index >= elements.len) return null;

return try analyser.resolveTypeOfNode(.{ .handle = node.handle, .node = elements[index] });
},
else => return null,
};
const node = scope_handle.toNode();
Expand Down Expand Up @@ -1692,26 +1701,13 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
var buffer: [2]Ast.Node.Index = undefined;
const array_init_info = tree.fullArrayInit(&buffer, node).?;

std.debug.assert(array_init_info.ast.elements.len != 0);

if (array_init_info.ast.type_expr != 0) blk: {
const array_ty = try analyser.resolveTypeOfNode(.{ .node = array_init_info.ast.type_expr, .handle = handle }) orelse break :blk;
return try array_ty.instanceTypeVal(analyser);
}

// try to infer the array type
const maybe_elem_ty = try analyser.resolveTypeOfNodeInternal(.{ .node = array_init_info.ast.elements[0], .handle = handle });
const elem_ty = if (maybe_elem_ty) |elem_ty| elem_ty.typeOf(analyser) else try Type.typeValFromIP(analyser, .type_type);

const elem_ty_ptr = try analyser.arena.allocator().create(Type);
elem_ty_ptr.* = elem_ty;

return Type{
.data = .{ .array = .{
.elem_count = @intCast(array_init_info.ast.elements.len),
.sentinel = .none,
.elem_ty = elem_ty_ptr,
} },
.data = .{ .other = node_handle },
.is_type_val = false,
};
},
Expand Down Expand Up @@ -2373,6 +2369,8 @@ pub const Type = struct {

/// - Error type: `Foo || Bar`, `Foo!Bar`
/// - Function: `fn () Foo`, `fn foo() Foo`
/// - `.{a,b}`
/// - `start..end`
other: NodeWithHandle,

/// - `@compileError("")`
Expand Down Expand Up @@ -4012,7 +4010,7 @@ pub const DeclWithHandle = struct {
}) orelse return null;
break :blk switch (node.data) {
.array => |array_info| try array_info.elem_ty.instanceTypeVal(analyser),
.container => try analyser.resolveTupleFieldType(node, pay.index),
.container, .other => try analyser.resolveTupleFieldType(node, pay.index),
else => null,
};
},
Expand Down
9 changes: 1 addition & 8 deletions src/features/completions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,7 @@ fn typeToCompletion(builder: *Builder, ty: Analyser.Type) error{OutOfMemory}!voi
try typeToCompletion(builder, rhs_ty);
}
},

.fn_proto,
.fn_proto_multi,
.fn_proto_one,
.fn_proto_simple,
.fn_decl,
=> {},
else => unreachable,
else => {},
},
.ip_index => |payload| try analyser_completions.dotCompletions(
builder.arena,
Expand Down
64 changes: 38 additions & 26 deletions src/features/semantic_tokens.zig
Original file line number Diff line number Diff line change
Expand Up @@ -312,30 +312,8 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
.aligned_var_decl,
=> {
const var_decl = tree.fullVarDecl(node).?;
try writeToken(builder, var_decl.visib_token, .keyword);
try writeToken(builder, var_decl.extern_export_token, .keyword);
try writeToken(builder, var_decl.threadlocal_token, .keyword);
try writeToken(builder, var_decl.comptime_token, .keyword);
try writeToken(builder, var_decl.ast.mut_token, .keyword);

if (try builder.analyser.resolveTypeOfNode(.{ .node = node, .handle = handle })) |decl_type| {
try colorIdentifierBasedOnType(builder, decl_type, var_decl.ast.mut_token + 1, false, .{ .declaration = true });
} else {
try writeTokenMod(builder, var_decl.ast.mut_token + 1, .variable, .{ .declaration = true });
}

try writeNodeTokens(builder, var_decl.ast.type_node);
try writeNodeTokens(builder, var_decl.ast.align_node);
try writeNodeTokens(builder, var_decl.ast.section_node);

if (var_decl.ast.init_node != 0) {
const equal_token = tree.firstToken(var_decl.ast.init_node) - 1;
if (token_tags[equal_token] == .equal) {
try writeToken(builder, equal_token, .operator);
}
}

try writeNodeTokens(builder, var_decl.ast.init_node);
const resolved_type = try builder.analyser.resolveTypeOfNode(.{ .node = node, .handle = handle });
try writeVarDecl(builder, var_decl, resolved_type);
},
.@"usingnamespace" => {
const first_token = tree.firstToken(node);
Expand Down Expand Up @@ -829,9 +807,14 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
.assign_destructure => {
const lhs_count = tree.extra_data[node_data[node].lhs];
const lhs_exprs = tree.extra_data[node_data[node].lhs + 1 ..][0..lhs_count];
const init_expr = node_data[node].rhs;

for (lhs_exprs) |lhs_node| {
try writeNodeTokens(builder, lhs_node);
const resolved_type = try builder.analyser.resolveTypeOfNode(.{ .node = init_expr, .handle = handle });

for (lhs_exprs, 0..) |lhs_node, index| {
const var_decl = tree.fullVarDecl(lhs_node).?;
const field_type = if (resolved_type) |ty| try builder.analyser.resolveTupleFieldType(ty, index) else null;
try writeVarDecl(builder, var_decl, field_type);
}

try writeToken(builder, main_token, .operator);
Expand Down Expand Up @@ -989,6 +972,35 @@ fn writeContainerField(builder: *Builder, node: Ast.Node.Index, container_decl:
}
}

fn writeVarDecl(builder: *Builder, var_decl: Ast.full.VarDecl, resolved_type: ?Analyser.Type) error{OutOfMemory}!void {
const tree = builder.handle.tree;
const token_tags = tree.tokens.items(.tag);

try writeToken(builder, var_decl.visib_token, .keyword);
try writeToken(builder, var_decl.extern_export_token, .keyword);
try writeToken(builder, var_decl.threadlocal_token, .keyword);
try writeToken(builder, var_decl.comptime_token, .keyword);
try writeToken(builder, var_decl.ast.mut_token, .keyword);

if (resolved_type) |decl_type| {
try colorIdentifierBasedOnType(builder, decl_type, var_decl.ast.mut_token + 1, false, .{ .declaration = true });
} else {
try writeTokenMod(builder, var_decl.ast.mut_token + 1, .variable, .{ .declaration = true });
}

try writeNodeTokens(builder, var_decl.ast.type_node);
try writeNodeTokens(builder, var_decl.ast.align_node);
try writeNodeTokens(builder, var_decl.ast.section_node);

if (var_decl.ast.init_node != 0) {
const equal_token = tree.firstToken(var_decl.ast.init_node) - 1;
if (token_tags[equal_token] == .equal) {
try writeToken(builder, equal_token, .operator);
}
try writeNodeTokens(builder, var_decl.ast.init_node);
}
}

fn writeIdentifier(builder: *Builder, name_token: Ast.Node.Index) error{OutOfMemory}!void {
const handle = builder.handle;
const tree = handle.tree;
Expand Down
22 changes: 20 additions & 2 deletions tests/lsp_features/completion.zig
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ test "symbol lookup on identifier named after primitive" {
});
}

test "assign destructure" {
test "var decl destructuring" {
try testCompletion(
\\test {
\\ const foo, var bar: u32 = .{42, 7};
Expand All @@ -191,7 +191,15 @@ test "assign destructure" {
.{ .label = "foo", .kind = .Constant, .detail = "comptime_int" },
.{ .label = "bar", .kind = .Variable, .detail = "u32" },
});
if (true) return error.SkipZigTest; // TODO
try testCompletion(
\\test {
\\ var foo, const bar = .{@as(u32, 42), @as(u64, 7)};
\\ <cursor>
\\}
, &.{
.{ .label = "foo", .kind = .Variable, .detail = "u32" },
.{ .label = "bar", .kind = .Constant, .detail = "u64" },
});
try testCompletion(
\\test {
\\ const S, const E = .{struct{}, enum{}};
Expand All @@ -201,6 +209,16 @@ test "assign destructure" {
.{ .label = "S", .kind = .Struct, .detail = "type" },
.{ .label = "E", .kind = .Enum, .detail = "type" },
});
try testCompletion(
\\test {
\\ const foo, const bar: u64, var baz = [_]u32{1, 2, 3};
\\ <cursor>
\\}
, &.{
.{ .label = "foo", .kind = .Constant, .detail = "u32" },
.{ .label = "bar", .kind = .Constant, .detail = "u64" },
.{ .label = "baz", .kind = .Variable, .detail = "u32" },
});
}

test "function" {
Expand Down
42 changes: 27 additions & 15 deletions tests/lsp_features/hover.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1111,48 +1111,45 @@ test "var decl alias" {
);
}

test "hover - destructuring" {
test "var decl destructuring" {
try testHover(
\\fn func() void {
\\ const f<cursor>oo, const bar = .{ 1, 2 };
\\test {
\\ const f<cursor>oo, const bar = .{ @as(u8, 1), @as(u16, 2), @as(u24, 3) };
\\}
,
\\```zig
\\foo
\\```
\\```zig
\\(comptime_int)
\\(u8)
\\```
);
try testHover(
\\fn func() void {
\\ const foo, const b<cursor>ar, const baz = .{ 1, 2, 3 };
\\test {
\\ const foo, const b<cursor>ar, const baz = .{ @as(u8, 1), @as(u16, 2), @as(u24, 3) };
\\}
,
\\```zig
\\bar
\\```
\\```zig
\\(comptime_int)
\\(u16)
\\```
);
try testHover(
\\fn thing() !struct {usize, isize} {
\\ return .{1, 2};
\\}
\\fn ex() void {
\\ const f<cursor>oo, const bar = try thing();
\\test {
\\ const foo, var b<cursor>ar: u32 = .{ 1, 2 };
\\}
,
\\```zig
\\foo
\\bar
\\```
\\```zig
\\(usize)
\\(u32)
\\```
);
try testHover(
\\fn func() void {
\\test {
\\ const foo, const b<cursor>ar: u32, const baz = undefined;
\\}
,
Expand All @@ -1163,6 +1160,21 @@ test "hover - destructuring" {
\\(u32)
\\```
);
try testHover(
\\fn thing() !struct {usize, isize} {
\\ return .{1, 2};
\\}
\\test {
\\ const f<cursor>oo, const bar = try thing();
\\}
,
\\```zig
\\foo
\\```
\\```zig
\\(usize)
\\```
);
}

test "escaped identifier" {
Expand Down
31 changes: 19 additions & 12 deletions tests/lsp_features/inlay_hints.zig
Original file line number Diff line number Diff line change
Expand Up @@ -208,18 +208,7 @@ test "hide redundant parameter names" {
.hide_redundant_param_names_last_token = true,
});
}
test "inlay destructuring" {
try testInlayHints(
\\fn func() void {
\\ const foo<comptime_int>, const bar<comptime_int> = .{1, 2};
\\}
, .{ .kind = .Type });
try testInlayHints(
\\fn func() void {
\\ const foo: comptime_int, const bar<comptime_int> = .{1, 2};
\\}
, .{ .kind = .Type });
}

test "var decl" {
try testInlayHints(
\\const a<@Vector(2,u8)> = @Vector(2, u8){1,2};
Expand Down Expand Up @@ -278,6 +267,24 @@ test "var decl" {
, .{ .kind = .Type });
}

test "var decl destructuring" {
try testInlayHints(
\\test {
\\ const foo<u32>, const bar<comptime_int> = .{@as(u32, 1), 2};
\\}
, .{ .kind = .Type });
try testInlayHints(
\\test {
\\ const foo: comptime_int, const bar<u64> = .{1, @as(u64, 7)};
\\}
, .{ .kind = .Type });
try testInlayHints(
\\test {
\\ const foo<u32>, const bar: u64, var baz<u32> = [_]u32{1, 2, 3};
\\}
, .{ .kind = .Type });
}

test "function alias" {
try testInlayHints(
\\fn foo(alpha: u32) void {
Expand Down
27 changes: 23 additions & 4 deletions tests/lsp_features/semantic_tokens.zig
Original file line number Diff line number Diff line change
Expand Up @@ -185,22 +185,41 @@ test "var decl" {

test "var decl destructure" {
try testSemanticTokens(
\\const foo = {
\\test {
\\ var alpha: bool, var beta = .{ 1, 2 };
\\};
, &.{
.{ "const", .keyword, .{} },
.{ "foo", .variable, .{ .declaration = true } },
.{ "=", .operator, .{} },
.{ "test", .keyword, .{} },

.{ "var", .keyword, .{} },
.{ "alpha", .variable, .{ .declaration = true } },
.{ "bool", .type, .{} },

.{ "var", .keyword, .{} },
.{ "beta", .variable, .{ .declaration = true } },

.{ "=", .operator, .{} },
.{ "1", .number, .{} },
.{ "2", .number, .{} },
});
try testSemanticTokens(
\\test {
\\ const S, const E = .{ struct {}, enum {} };
\\};
, &.{
.{ "test", .keyword, .{} },

.{ "const", .keyword, .{} },
.{ "S", .namespace, .{ .declaration = true } },

.{ "const", .keyword, .{} },
.{ "E", .@"enum", .{ .declaration = true } },

.{ "=", .operator, .{} },

.{ "struct", .keyword, .{} },
.{ "enum", .keyword, .{} },
});
}

test "local var decl" {
Expand Down