diff --git a/src/features/goto.zig b/src/features/goto.zig
index 18bdc16a9..0b107b899 100644
--- a/src/features/goto.zig
+++ b/src/features/goto.zig
@@ -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);
}
diff --git a/src/features/references.zig b/src/features/references.zig
index af80c440e..ee80c52b6 100644
--- a/src/features/references.zig
+++ b/src/features/references.zig
@@ -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(
diff --git a/tests/lsp_features/definition.zig b/tests/lsp_features/definition.zig
index a6cba99ac..784b32ede 100644
--- a/tests/lsp_features/definition.zig
+++ b/tests/lsp_features/definition.zig
@@ -32,12 +32,12 @@ test "global variable" {
try testDefinition(
\\const S = struct { alpha: u32 };
- \\const <>s: S = S{ .alpha = 5 };
+ \\const <>s: S = S{ .alpha = 5 };
);
try testDefinition(
\\const S = struct { alpha: u32 };
- \\const <>s = S{ .alpha = 5 };
+ \\const <>s = S{ .alpha = 5 };
);
}
@@ -92,11 +92,11 @@ test "function parameter" {
test "inferred struct init" {
try testDefinition(
- \\const S = struct { alpha: u32 };
+ \\const S = struct { alpha: u32 };
\\const foo: S = .<>{ .alpha = 5 };
);
try testDefinition(
- \\const S = struct { alpha: u32 };
+ \\const S = struct { alpha: u32 };
\\fn f(_: S) void {}
\\const foo = f(<>.{ .alpha = 5 });
);
@@ -137,7 +137,7 @@ test "decl literal on generic type" {
test "decl literal pointer" {
try testDefinition(
- \\const S = struct {
+ \\const S = struct {
\\ const value: S = .{};
\\ const ptr: *const S = &value;
\\};
@@ -148,7 +148,7 @@ test "decl literal pointer" {
try testDefinition(
\\const S = struct {
\\ const value: S = .{};
- \\ fn pointerFn() *const S {
+ \\ fn pointerFn() *const S {
\\ return &value;
\\ }
\\};
@@ -163,7 +163,7 @@ test "capture" {
\\test {
\\ const S = struct {};
\\ var maybe: ?S = 5;
- \\ if (maybe) |<>some| {}
+ \\ if (maybe) |<>some| {}
\\}
);
if (true) return error.SkipZigTest; // TODO
@@ -172,7 +172,7 @@ test "capture" {
try testDefinition(
\\test {
\\ var maybe: ?u32 = 5;
- \\ if (maybe) |<>some| {}
+ \\ if (maybe) |<>some| {}
\\}
);
}
@@ -252,7 +252,7 @@ test "escaped identifier - global" {
test "escaped identifier - enum literal" {
try testDefinition(
- \\const E = enum { @"foo bar" };
+ \\const E = enum { @"foo bar" };
\\const e: E = .<>@"foo bar";
);
}
@@ -271,7 +271,7 @@ test "multiline builder pattern" {
\\ fn add(foo: Foo) Foo {}
\\ fn remove(foo: Foo) Foo {}
\\ fn process(foo: Foo) Foo {}
- \\ fn finalize(_: Foo) void {}
+ \\ fn finalize(_: Foo) void {}
\\};
\\test {
\\ var builder = Foo{};
@@ -296,7 +296,7 @@ test "block and decl with same name" {
);
try testDefinition(
\\const x = x: {
- \\ const x: u8 = 1;
+ \\ const x: u8 = 1;
\\ break :x <>x;
\\};
\\_ = x;
@@ -322,7 +322,7 @@ test "non labeled break" {
\\}
);
try testDefinition(
- \\const num: usize = 5;
+ \\const num: usize = 5;
\\return while (true) {
\\ break num<>;
\\};
@@ -332,40 +332,63 @@ test "non labeled break" {
test "type definition unwraps error unions, optionals, pointers" {
try testDefinition(
\\const S = struct {};
- \\const <>foo: error{}!S = .{};
+ \\const <>foo: error{}!S = .{};
);
try testDefinition(
\\const S = struct {};
- \\const <>foo: ?S = .{};
+ \\const <>foo: ?S = .{};
);
try testDefinition(
\\const S = struct {};
- \\const <>foo: *const S = &.{};
+ \\const <>foo: *const S = &.{};
);
try testDefinition(
\\const S = struct {};
- \\const <>foo: error{}!?*const S = &.{};
+ \\const <>foo: error{}!?*const S = &.{};
);
}
test "builtins" {
try testDefinition(
\\const S = struct {
- \\ const Self = <>@This();
+ \\ const Self = <>@This();
\\};
);
try testDefinition(
\\const S = struct {
- \\ const Self = @This();
+ \\ const Self = @This();
\\};
\\const foo: <>S = .{};
);
try testDefinition(
- \\const S = @This();
+ \\const S = @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 @"null" = undefined;
+ \\const foo = null;
+ \\const bar = <>@"null";
+ );
+ try testDefinition(
+ \\const @"i32" = undefined;
+ \\const foo = i32;
+ \\const bar = <>@"i32";
+ );
+}
+
/// - use `<>` to indicate the cursor position
/// - use `content` to set the expected range of the declaration
/// - use `content` to set the expected range of the definition
@@ -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 , or \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);
@@ -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| {
@@ -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| {
@@ -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| {
diff --git a/tests/lsp_features/references.zig b/tests/lsp_features/references.zig
index dfbc45e62..c251595e8 100644
--- a/tests/lsp_features/references.zig
+++ b/tests/lsp_features/references.zig
@@ -637,6 +637,19 @@ test "matching control flow - unlabeled switch" {
);
}
+test "escaped identifier with same name as primitive" {
+ try testSimpleReferences(
+ \\const @"null" = undefined;
+ \\const foo = null;
+ \\const bar = @"null";
+ );
+ try testSimpleReferences(
+ \\const @"i32" = undefined;
+ \\const foo = i32;
+ \\const bar = @"i32";
+ );
+}
+
fn testSimpleReferences(source: []const u8) !void {
var phr = try helper.collectClearPlaceholders(allocator, source);
defer phr.deinit(allocator);