Skip to content
Open
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
7 changes: 7 additions & 0 deletions src/features/goto.zig
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ fn gotoDefinitionGlobal(

const name_token, const name_loc = offsets.identifierTokenAndLocFromIndex(&handle.tree, pos_index) orelse return null;
const name = offsets.locToSlice(handle.tree.source, name_loc);
const is_escaped_identifier = handle.tree.source[handle.tree.tokenStart(name_token)] == '@';

if (!is_escaped_identifier) {
if (std.mem.eql(u8, name, "_")) return null;
if (try analyser.resolvePrimitive(name)) |_| return null;
}

const decl = (try analyser.lookupSymbolGlobal(handle, name, pos_index)) orelse return null;
return try gotoDefinitionSymbol(analyser, offsets.tokenToRange(&handle.tree, name_token, offset_encoding), decl, kind, offset_encoding);
}
Expand Down
7 changes: 7 additions & 0 deletions src/features/references.zig
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ const Builder = struct {
else => unreachable,
};
const name = offsets.identifierTokenToNameSlice(tree, name_token);
const is_escaped_identifier = tree.source[tree.tokenStart(name_token)] == '@';

if (!is_escaped_identifier) {
if (std.mem.eql(u8, name, "_")) return;
if (try builder.analyser.resolvePrimitive(name)) |_| return;
}

if (!std.mem.eql(u8, name, target_symbol_name)) return;

const candidate = try builder.analyser.lookupSymbolGlobal(
Expand Down
92 changes: 51 additions & 41 deletions tests/lsp_features/definition.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ test "global variable" {

try testDefinition(
\\const S = <tdef>struct</tdef> { alpha: u32 };
\\const <>s: S = S{ .alpha = 5 };
\\const <def><decl><>s</decl></def>: S = S{ .alpha = 5 };
);

try testDefinition(
\\const S = <tdef>struct</tdef> { alpha: u32 };
\\const <>s = S{ .alpha = 5 };
\\const <def><decl><>s</decl></def> = S{ .alpha = 5 };
);
}

Expand Down Expand Up @@ -92,11 +92,11 @@ test "function parameter" {

test "inferred struct init" {
try testDefinition(
\\const S = <def>struct</def> { alpha: u32 };
\\const S = <tdef><def>struct</def></tdef> { alpha: u32 };
\\const foo: S = .<>{ .alpha = 5 };
);
try testDefinition(
\\const S = <def>struct</def> { alpha: u32 };
\\const S = <tdef><def>struct</def></tdef> { alpha: u32 };
\\fn f(_: S) void {}
\\const foo = f(<>.{ .alpha = 5 });
);
Expand Down Expand Up @@ -137,7 +137,7 @@ test "decl literal on generic type" {

test "decl literal pointer" {
try testDefinition(
\\const S = struct {
\\const S = <tdef>struct</tdef> {
\\ const value: S = .{};
\\ const <def><decl>ptr</decl></def>: *const S = &value;
\\};
Expand All @@ -148,7 +148,7 @@ test "decl literal pointer" {
try testDefinition(
\\const S = struct {
\\ const value: S = .{};
\\ fn <def><decl>pointerFn</decl></def>() *const S {
\\ <tdef>fn</tdef> <def><decl>pointerFn</decl></def>() *const S {
\\ return &value;
\\ }
\\};
Expand All @@ -163,7 +163,7 @@ test "capture" {
\\test {
\\ const S = <tdef>struct</tdef> {};
\\ var maybe: ?S = 5;
\\ if (maybe) |<>some| {}
\\ if (maybe) |<def><decl><>some</decl></def>| {}
\\}
);
if (true) return error.SkipZigTest; // TODO
Expand All @@ -172,7 +172,7 @@ test "capture" {
try testDefinition(
\\test {
\\ var maybe: <tdef>?u32</tdef> = 5;
\\ if (maybe) |<>some| {}
\\ if (maybe) |<def><decl><>some</decl></def>| {}
\\}
);
}
Expand Down Expand Up @@ -252,7 +252,7 @@ test "escaped identifier - global" {

test "escaped identifier - enum literal" {
try testDefinition(
\\const E = enum { <def><decl>@"foo bar"</decl></def> };
\\const E = <tdef>enum</tdef> { <def><decl>@"foo bar"</decl></def> };
\\const e: E = .<origin><>@"foo bar"</origin>;
);
}
Expand All @@ -271,7 +271,7 @@ test "multiline builder pattern" {
\\ fn add(foo: Foo) Foo {}
\\ fn remove(foo: Foo) Foo {}
\\ fn process(foo: Foo) Foo {}
\\ fn <def>finalize</def>(_: Foo) void {}
\\ <tdef>fn</tdef> <def><decl>finalize</decl></def>(_: Foo) void {}
\\};
\\test {
\\ var builder = Foo{};
Expand All @@ -296,7 +296,7 @@ test "block and decl with same name" {
);
try testDefinition(
\\const x = x: {
\\ const <def><decl>x</decl></def>: u8 = 1;
\\ const <def><decl>x</decl></def>: <tdef>u8</tdef> = 1;
\\ break :x <>x;
\\};
\\_ = x;
Expand All @@ -322,7 +322,7 @@ test "non labeled break" {
\\}
);
try testDefinition(
\\const <def><decl>num</decl></def>: usize = 5;
\\const <def><decl>num</decl></def>: <tdef>usize</tdef> = 5;
\\return while (true) {
\\ break num<>;
\\};
Expand All @@ -332,40 +332,63 @@ test "non labeled break" {
test "type definition unwraps error unions, optionals, pointers" {
try testDefinition(
\\const S = <tdef>struct</tdef> {};
\\const <>foo: error{}!S = .{};
\\const <def><decl><>foo</decl></def>: error{}!S = .{};
);
try testDefinition(
\\const S = <tdef>struct</tdef> {};
\\const <>foo: ?S = .{};
\\const <def><decl><>foo</decl></def>: ?S = .{};
);
try testDefinition(
\\const S = <tdef>struct</tdef> {};
\\const <>foo: *const S = &.{};
\\const <def><decl><>foo</decl></def>: *const S = &.{};
);
try testDefinition(
\\const S = <tdef>struct</tdef> {};
\\const <>foo: error{}!?*const S = &.{};
\\const <def><decl><>foo</decl></def>: error{}!?*const S = &.{};
);
}

test "builtins" {
try testDefinition(
\\const S = struct {
\\ const <def><decl>Self</decl></def> = <>@This();
\\ const <tdef><def><decl>Self</decl></def></tdef> = <>@This();
\\};
);
try testDefinition(
\\const <decl>S</decl> = struct {
\\ const <def>Self</def> = @This();
\\ const <tdef><def>Self</def></tdef> = @This();
\\};
\\const foo: <>S = .{};
);
try testDefinition(
\\const <def><decl>S</decl></def> = @This();
\\const <tdef><def><decl>S</decl></def></tdef> = @This();
\\const foo: <>S = .{};
);
}

test "escaped identifier with same name as primitive" {
try testDefinition(
\\const @"null" = undefined;
\\const foo = <>null;
\\const bar = @"null";
);
try testDefinition(
\\const @"i32" = undefined;
\\const foo = <>i32;
\\const bar = @"i32";
);
try testDefinition(
\\const <def><decl>@"null"</decl></def> = undefined;
\\const foo = null;
\\const bar = <>@"null";
);
try testDefinition(
\\const <def><decl>@"i32"</decl></def> = undefined;
\\const foo = i32;
\\const bar = <>@"i32";
);
}

/// - use `<>` to indicate the cursor position
/// - use `<decl>content</decl>` to set the expected range of the declaration
/// - use `<def>content</def>` to set the expected range of the definition
Expand Down Expand Up @@ -433,34 +456,15 @@ fn testDefinition(source: []const u8) !void {
const type_definition_loc: ?offsets.Loc = try parseTaggedLoc(source, phr, "tdef");
const origin_loc: ?offsets.Loc = try parseTaggedLoc(source, phr, "origin");

if (declaration_loc == null and
definition_loc == null and
type_definition_loc == null)
{
std.debug.print("must specify at least one sub-test with <decl>, <def> or <tdef>\n", .{});
return error.NoChecksSpecified;
}

const cursor_position = offsets.indexToPosition(phr.new_source, cursor_index, ctx.server.offset_encoding);

const declaration_params: types.declaration.Params = .{ .textDocument = .{ .uri = test_uri.raw }, .position = cursor_position };
const definition_params: types.Definition.Params = .{ .textDocument = .{ .uri = test_uri.raw }, .position = cursor_position };
const type_definition_params: types.type_definition.Params = .{ .textDocument = .{ .uri = test_uri.raw }, .position = cursor_position };

const maybe_declaration_response = if (declaration_loc != null)
try ctx.server.sendRequestSync(ctx.arena.allocator(), "textDocument/declaration", declaration_params)
else
null;

const maybe_definition_response = if (definition_loc != null)
try ctx.server.sendRequestSync(ctx.arena.allocator(), "textDocument/definition", definition_params)
else
null;

const maybe_type_definition_response = if (type_definition_loc != null)
try ctx.server.sendRequestSync(ctx.arena.allocator(), "textDocument/typeDefinition", type_definition_params)
else
null;
const maybe_declaration_response = try ctx.server.sendRequestSync(ctx.arena.allocator(), "textDocument/declaration", declaration_params);
const maybe_definition_response = try ctx.server.sendRequestSync(ctx.arena.allocator(), "textDocument/definition", definition_params);
const maybe_type_definition_response = try ctx.server.sendRequestSync(ctx.arena.allocator(), "textDocument/typeDefinition", type_definition_params);

if (maybe_declaration_response) |response| {
try std.testing.expect(response == .definition_links);
Expand All @@ -472,6 +476,8 @@ fn testDefinition(source: []const u8) !void {
try error_builder.msgAtLoc("expected declaration here!", test_uri.raw, expected_loc, .err, .{});
try error_builder.msgAtLoc("actual declaration here", test_uri.raw, actual_loc, .err, .{});
}
} else {
try error_builder.msgAtLoc("unexpected declaration here", test_uri.raw, actual_loc, .err, .{});
}
const actual_origin_loc = offsets.rangeToLoc(phr.new_source, response.definition_links[0].originSelectionRange.?, ctx.server.offset_encoding);
if (origin_loc) |expected_origin_loc| {
Expand All @@ -494,6 +500,8 @@ fn testDefinition(source: []const u8) !void {
try error_builder.msgAtLoc("expected definition here!", test_uri.raw, expected_loc, .err, .{});
try error_builder.msgAtLoc("actual definition here", test_uri.raw, actual_loc, .err, .{});
}
} else {
try error_builder.msgAtLoc("unexpected definition here", test_uri.raw, actual_loc, .err, .{});
}
const actual_origin_loc = offsets.rangeToLoc(phr.new_source, response.definition_links[0].originSelectionRange.?, ctx.server.offset_encoding);
if (origin_loc) |expected_origin_loc| {
Expand All @@ -516,6 +524,8 @@ fn testDefinition(source: []const u8) !void {
try error_builder.msgAtLoc("expected type definition here!", test_uri.raw, expected_loc, .err, .{});
try error_builder.msgAtLoc("actual type definition here", test_uri.raw, actual_loc, .err, .{});
}
} else {
try error_builder.msgAtLoc("unexpected type definition here", test_uri.raw, actual_loc, .err, .{});
}
const actual_origin_loc = offsets.rangeToLoc(phr.new_source, response.definition_links[0].originSelectionRange.?, ctx.server.offset_encoding);
if (origin_loc) |expected_origin_loc| {
Expand Down
13 changes: 13 additions & 0 deletions tests/lsp_features/references.zig
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,19 @@ test "matching control flow - unlabeled switch" {
);
}

test "escaped identifier with same name as primitive" {
try testSimpleReferences(
\\const @"null"<cursor> = undefined;
\\const foo = null;
\\const bar = <loc>@"null"</loc>;
);
try testSimpleReferences(
\\const @"i32"<cursor> = undefined;
\\const foo = i32;
\\const bar = <loc>@"i32"</loc>;
);
}

fn testSimpleReferences(source: []const u8) !void {
var phr = try helper.collectClearPlaceholders(allocator, source);
defer phr.deinit(allocator);
Expand Down