Skip to content

Commit 6d49b0f

Browse files
committed
fix inefficient semanticTokens/range at the end of files
1 parent f298676 commit 6d49b0f

4 files changed

Lines changed: 91 additions & 2 deletions

File tree

src/ast.zig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,28 @@ pub fn nodesOverlappingIndexIncludingParseErrors(allocator: std.mem.Allocator, t
16411641
return nodes;
16421642
}
16431643

1644+
/// Returns a shallow list of nodes that overlap with a loc, i.e. may contain a `fn_decl` node but not its prototype or body.
1645+
/// Caller owns returned memory.
1646+
pub fn nodesIntersectingLoc(allocator: std.mem.Allocator, tree: *const Ast, loc: offsets.Loc) error{OutOfMemory}![]Ast.Node.Index {
1647+
std.debug.assert(loc.start <= loc.end and loc.end <= tree.source.len);
1648+
1649+
var nodes: std.ArrayList(Ast.Node.Index) = .empty;
1650+
defer nodes.deinit(allocator);
1651+
1652+
var walker: Walker = try .init(allocator, tree, .root);
1653+
defer walker.deinit(allocator);
1654+
1655+
while (try walker.nextIgnoreClose(allocator, tree)) |node| {
1656+
const node_loc = offsets.nodeToLoc(tree, node);
1657+
if (offsets.locIntersect(loc, node_loc)) {
1658+
try nodes.append(allocator, node);
1659+
}
1660+
walker.skip();
1661+
}
1662+
1663+
return try nodes.toOwnedSlice(allocator);
1664+
}
1665+
16441666
/// returns a list of nodes that together encloses the given source code range
16451667
/// caller owns the returned memory
16461668
pub fn nodesAtLoc(allocator: std.mem.Allocator, tree: *const Ast, loc: offsets.Loc) error{OutOfMemory}![]Ast.Node.Index {

src/features/inlay_hints.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ pub fn writeRangeInlayHint(
565565
.hover_kind = hover_kind,
566566
};
567567

568-
const nodes = try ast.nodesAtLoc(arena, &handle.tree, loc);
568+
const nodes = try ast.nodesIntersectingLoc(arena, &handle.tree, loc);
569569

570570
for (nodes) |child| {
571571
try writeNodeInlayHint(&builder, &handle.tree, child);

src/features/semantic_tokens.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ pub fn writeSemanticTokens(
11731173
.overlappingTokenSupport = overlappingTokenSupport,
11741174
};
11751175

1176-
var nodes = if (loc) |l| try ast.nodesAtLoc(arena, &handle.tree, l) else handle.tree.rootDecls();
1176+
var nodes = if (loc) |l| try ast.nodesIntersectingLoc(arena, &handle.tree, l) else handle.tree.rootDecls();
11771177
if (nodes.len == 1 and nodes[0] == .root) {
11781178
nodes = handle.tree.rootDecls();
11791179
}

tests/utility/ast.zig

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,70 @@ fn testNodesAtLoc(source: []const u8) !void {
115115
return error.LocEndMismatch;
116116
}
117117
}
118+
119+
test "nodesIntersectingLoc" {
120+
try testNodesIntersectingLoc(
121+
\\<loc>const a = 0;
122+
\\<loc>const b = 0;
123+
, &.{.simple_var_decl});
124+
try testNodesIntersectingLoc(
125+
\\<loc> const a = 0;
126+
\\const <loc> b = 0;
127+
, &.{ .simple_var_decl, .simple_var_decl });
128+
try testNodesIntersectingLoc(
129+
\\const a = 0;
130+
\\fn foo(_:<loc> u32) void {
131+
\\ const b = 0;
132+
\\<loc>}
133+
\\const c = 0;
134+
, &.{.fn_decl});
135+
try testNodesIntersectingLoc(
136+
\\const a = 0;
137+
\\fn foo(_: u32) void {<loc>
138+
\\ const b = 0;
139+
\\}
140+
\\const <loc> c = 0;
141+
, &.{ .fn_decl, .simple_var_decl });
142+
}
143+
144+
fn testNodesIntersectingLoc(
145+
source: [:0]const u8,
146+
expected_tags: []const std.zig.Ast.Node.Tag,
147+
) !void {
148+
var ccp = try helper.collectClearPlaceholders(allocator, source);
149+
defer ccp.deinit(allocator);
150+
151+
const locs = ccp.locations.items(.new);
152+
std.debug.assert(ccp.locations.len == 2);
153+
154+
const loc: offsets.Loc = .{
155+
.start = locs[0].start,
156+
.end = locs[1].start,
157+
};
158+
159+
const new_source = try allocator.dupeSentinel(u8, ccp.new_source, 0);
160+
defer allocator.free(new_source);
161+
162+
var tree: std.zig.Ast = try .parse(allocator, new_source, .zig);
163+
defer tree.deinit(allocator);
164+
165+
const nodes = try ast.nodesIntersectingLoc(allocator, &tree, loc);
166+
defer allocator.free(nodes);
167+
168+
const uri = "file.zig";
169+
var error_builder: ErrorBuilder = .init(allocator);
170+
defer error_builder.deinit();
171+
errdefer error_builder.writeDebug();
172+
173+
try error_builder.addFile(uri, new_source);
174+
175+
for (nodes, expected_tags) |node, tag| {
176+
if (tree.nodeTag(node) != tag) {
177+
try error_builder.msgAtIndex("expected '{t}' found '{t}'", uri, loc.start, .err, .{
178+
tag,
179+
tree.nodeTag(node),
180+
});
181+
return error.NodeMismatch;
182+
}
183+
}
184+
}

0 commit comments

Comments
 (0)