From 79e5015fb1742cc347fc80e223bc1227e8119497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sun, 1 Mar 2026 08:30:13 -0800 Subject: [PATCH 1/2] tests/definition: fail tests on unexpected decl/def/tdef --- tests/lsp_features/definition.zig | 69 +++++++++++++------------------ 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/tests/lsp_features/definition.zig b/tests/lsp_features/definition.zig index a6cba99ac..ecf866208 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,36 +332,36 @@ 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 = .{}; ); } @@ -433,34 +433,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 +453,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 +477,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 +501,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| { From be235073dc1b84544fbf98701ca77d76cb2f1b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Sun, 1 Mar 2026 08:33:43 -0800 Subject: [PATCH 2/2] Fix goto and references involving primitive-like names --- src/features/goto.zig | 7 +++++++ src/features/references.zig | 7 +++++++ tests/lsp_features/definition.zig | 23 +++++++++++++++++++++++ tests/lsp_features/references.zig | 13 +++++++++++++ 4 files changed, 50 insertions(+) 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 ecf866208..784b32ede 100644 --- a/tests/lsp_features/definition.zig +++ b/tests/lsp_features/definition.zig @@ -366,6 +366,29 @@ test "builtins" { ); } +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 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);