Skip to content

Commit bb6d647

Browse files
overhaul code analysis of generic types (#2324)
fixes #1601 fixes #2274
1 parent dc1f664 commit bb6d647

File tree

12 files changed

+1240
-643
lines changed

12 files changed

+1240
-643
lines changed

src/DocumentScope.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ pub const Scope = struct {
353353
};
354354

355355
pub const OptionalIndex = enum(u32) {
356+
root,
356357
none = std.math.maxInt(u32),
357358
_,
358359

src/analysis.zig

Lines changed: 680 additions & 551 deletions
Large diffs are not rendered by default.

src/ast.zig

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,11 +1572,46 @@ pub fn nodesOverlappingIndex(allocator: std.mem.Allocator, tree: Ast, index: usi
15721572
};
15731573

15741574
var context: Context = .{ .index = index, .allocator = allocator };
1575+
defer context.nodes.deinit(allocator);
15751576
try iterateChildren(tree, .root, &context, error{OutOfMemory}, Context.append);
15761577
try context.nodes.append(allocator, .root);
15771578
return try context.nodes.toOwnedSlice(allocator);
15781579
}
15791580

1581+
/// returns a list of nodes that overlap with the given source code index.
1582+
/// the list may include nodes that were discarded during error recovery in the Zig parser.
1583+
/// sorted from smallest to largest.
1584+
/// caller owns the returned memory.
1585+
/// this function can be removed when the parser has been improved.
1586+
pub fn nodesOverlappingIndexIncludingParseErrors(allocator: std.mem.Allocator, tree: Ast, source_index: usize) error{OutOfMemory}![]Ast.Node.Index {
1587+
const NodeLoc = struct {
1588+
node: Ast.Node.Index,
1589+
loc: offsets.Loc,
1590+
1591+
fn lessThan(_: void, lhs: @This(), rhs: @This()) bool {
1592+
return rhs.loc.start < lhs.loc.start and lhs.loc.end < rhs.loc.end;
1593+
}
1594+
};
1595+
1596+
var node_locs: std.ArrayListUnmanaged(NodeLoc) = .empty;
1597+
defer node_locs.deinit(allocator);
1598+
for (0..tree.nodes.len) |i| {
1599+
const node: Ast.Node.Index = @enumFromInt(i);
1600+
const loc = offsets.nodeToLoc(tree, node);
1601+
if (loc.start <= source_index and source_index <= loc.end) {
1602+
try node_locs.append(allocator, .{ .node = node, .loc = loc });
1603+
}
1604+
}
1605+
1606+
std.mem.sort(NodeLoc, node_locs.items, {}, NodeLoc.lessThan);
1607+
1608+
const nodes = try allocator.alloc(Ast.Node.Index, node_locs.items.len);
1609+
for (node_locs.items, nodes) |node_loc, *node| {
1610+
node.* = node_loc.node;
1611+
}
1612+
return nodes;
1613+
}
1614+
15801615
/// returns a list of nodes that together encloses the given source code range
15811616
/// caller owns the returned memory
15821617
pub fn nodesAtLoc(allocator: std.mem.Allocator, tree: Ast, loc: offsets.Loc) error{OutOfMemory}![]Ast.Node.Index {

src/features/completions.zig

Lines changed: 93 additions & 70 deletions
Large diffs are not rendered by default.

src/features/hover.zig

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ fn hoverSymbolRecursive(
4141

4242
const def_str = switch (decl_handle.decl) {
4343
.ast_node => |node| def: {
44-
if (try analyser.resolveVarDeclAlias(.of(node, handle))) |result| {
44+
if (try analyser.resolveVarDeclAlias(.{
45+
.node_handle = .of(node, handle),
46+
.container_type = decl_handle.container_type,
47+
})) |result| {
4548
return try hoverSymbolRecursive(analyser, arena, result, markup_kind, doc_strings);
4649
}
4750

src/features/semantic_tokens.zig

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ fn colorIdentifierBasedOnType(
277277
new_tok_mod.generic = true;
278278
}
279279

280-
const has_self_param = Analyser.hasSelfParam(type_node);
280+
const has_self_param = builder.analyser.hasSelfParam(type_node);
281281

282282
try writeTokenMod(builder, target_tok, if (has_self_param) .method else .function, new_tok_mod);
283283
} else {
@@ -419,8 +419,13 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
419419
is_generic = func_ty.isGenericFunc();
420420
if (func_ty.isTypeFunc()) {
421421
func_name_tok_type = .type;
422-
} else if (Analyser.hasSelfParam(func_ty)) {
423-
func_name_tok_type = .method;
422+
} else {
423+
const container_ty = try builder.analyser.innermostContainer(handle, tree.tokenStart(fn_proto.ast.fn_token));
424+
if (container_ty.data.container.scope_handle.scope != .root and
425+
Analyser.firstParamIs(func_ty, container_ty))
426+
{
427+
func_name_tok_type = .method;
428+
}
424429
}
425430
}
426431

@@ -620,7 +625,8 @@ fn writeNodeTokens(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!v
620625

621626
if (try builder.analyser.resolveTypeOfNode(.of(type_expr, handle))) |struct_type| {
622627
switch (struct_type.data) {
623-
.container => |scope_handle| {
628+
.container => |info| {
629+
const scope_handle = info.scope_handle;
624630
field_token_type = fieldTokenType(scope_handle.toNode(), scope_handle.handle, false);
625631
},
626632
else => {},
@@ -1131,7 +1137,7 @@ fn writeFieldAccess(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!
11311137

11321138
try writeNodeTokens(builder, lhs_node);
11331139

1134-
const lhs = try builder.analyser.resolveTypeOfNode(.{ .node = lhs_node, .handle = handle }) orelse {
1140+
const lhs = try builder.analyser.resolveTypeOfNode(.of(lhs_node, handle)) orelse {
11351141
try writeToken(builder, field_name_token, .variable);
11361142
return;
11371143
};
@@ -1148,7 +1154,7 @@ fn writeFieldAccess(builder: *Builder, node: Ast.Node.Index) error{OutOfMemory}!
11481154
const decl_node = decl_type.decl.ast_node;
11491155
if (!decl_type.handle.tree.nodeTag(decl_node).isContainerField()) break :field_blk;
11501156
if (lhs_type.data != .container) break :field_blk;
1151-
const scope_handle = lhs_type.data.container;
1157+
const scope_handle = lhs_type.data.container.scope_handle;
11521158
const tt = fieldTokenType(
11531159
scope_handle.toNode(),
11541160
scope_handle.handle,

src/features/signature_help.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn fnProtoToSignatureInfo(
3636
})});
3737

3838
const arg_idx = if (skip_self_param) blk: {
39-
const has_self_param = Analyser.hasSelfParam(func_type);
39+
const has_self_param = analyser.hasSelfParam(func_type);
4040
break :blk commas + @intFromBool(has_self_param);
4141
} else commas;
4242

tests/analysis/generics.zig

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
fn Foo(T: type) type {
2+
return struct {
3+
fn bar(U: type, t: ?T, u: ?U) void {
4+
_ = .{ t, u };
5+
}
6+
7+
fn baz(U: type, t: T, u: U) T {
8+
return t + u;
9+
}
10+
11+
fn qux(U: type, t: T, u: U) @TypeOf(t, u) {
12+
return t + u;
13+
}
14+
};
15+
}
16+
17+
const foo = Foo(u8){};
18+
// ^^^ (Foo(u8))()
19+
20+
// TODO this should be `fn (U: type, ?u8, ?U) void`
21+
const bar_fn = Foo(u8).bar;
22+
// ^^^^^^ (fn (type, ?u8, ?U) void)()
23+
24+
const bar_call = Foo(u8).bar(i32, null, null);
25+
// ^^^^^^^^ (void)()
26+
27+
// TODO this should be `fn (U: type, i32, U) i32`
28+
const baz_fn = Foo(i32).baz;
29+
// ^^^^^^ (fn (type, i32, U) i32)()
30+
31+
const baz_call = Foo(i32).baz(u8, -42, 42);
32+
// ^^^^^^^^ (i32)()
33+
34+
// TODO this should be `fn (U: type, u8, U) anytype`
35+
const qux_fn = Foo(u8).qux;
36+
// ^^^^^^ (fn (type, u8, U) u8)()
37+
38+
// TODO this should be `i32`
39+
const qux_call = Foo(u8).qux(i32, 42, -42);
40+
// ^^^^^^^^ (u8)()
41+
42+
fn fizz(T: type) ?fn () error{}!struct { ??T } {
43+
return null;
44+
}
45+
46+
// TODO this should be `fn (T: type) ?fn () error{}!struct { ??T })()`
47+
const fizz_fn = fizz;
48+
// ^^^^^^^ (fn (type) ?fn () error{}!struct { ??T })()
49+
50+
const fizz_call = fizz(u8);
51+
// ^^^^^^^^^ (?fn () error{}!struct { ??u8 })()
52+
53+
comptime {
54+
// Use @compileLog to verify the expected type with the compiler:
55+
// @compileLog(foo);
56+
}
57+
58+
fn Point1(comptime T: type) type {
59+
return struct {
60+
x: T,
61+
y: T,
62+
fn normSquared(self: Point1(T)) T {
63+
_ = self;
64+
// ^^^^ (Point1(T))()
65+
}
66+
};
67+
}
68+
69+
fn parameter(comptime T: type, in: T) void {
70+
_ = in;
71+
// ^^ (T)()
72+
}
73+
74+
fn taggedUnion(comptime T: type, in: union(enum) { a: T, b: T }) void {
75+
switch (in) {
76+
.a => |a| {
77+
_ = a;
78+
// ^ (T)()
79+
},
80+
.b => |b| {
81+
_ = b;
82+
// ^ (T)()
83+
},
84+
}
85+
}
86+
87+
fn Option(comptime T: type) type {
88+
return struct {
89+
item: ?T,
90+
const none: @This() = undefined;
91+
const alias = none;
92+
const default = init();
93+
fn init() @This() {}
94+
};
95+
}
96+
97+
const option_none: Option(u8) = .none;
98+
// ^^^^^ (Option(u8))()
99+
100+
const option_alias: Option(u8) = .alias;
101+
// ^^^^^^ (Option(u8))()
102+
103+
const option_default: Option(u8) = .default;
104+
// ^^^^^^^^ (Option(u8))()
105+
106+
const option_init: Option(u8) = .init();
107+
// ^^^^^ (fn () Option(u8))()
108+
109+
fn GenericUnion(T: type) type {
110+
return union {
111+
field: T,
112+
const decl: T = undefined;
113+
};
114+
}
115+
116+
const generic_union_decl = GenericUnion(u8).decl;
117+
// ^^^^^^^^^^^^^^^^^^ (u8)()
118+
119+
const generic_union: GenericUnion(u8) = .{ .field = 1 };
120+
// ^^^^^^^^^^^^^ (GenericUnion(u8))()
121+
122+
const generic_union_field = generic_union.field;
123+
// ^^^^^^^^^^^^^^^^^^^ (u8)()
124+
125+
const generic_union_tag = GenericUnion(u8).field;
126+
// ^^^^^^^^^^^^^^^^^ (unknown)()
127+
128+
fn GenericTaggedUnion(T: type) type {
129+
return union(enum) {
130+
field: T,
131+
const decl: T = undefined;
132+
};
133+
}
134+
135+
const generic_tagged_union_decl = GenericTaggedUnion(u8).decl;
136+
// ^^^^^^^^^^^^^^^^^^^^^^^^^ (u8)()
137+
138+
const generic_tagged_union: GenericTaggedUnion(u8) = .{ .field = 1 };
139+
// ^^^^^^^^^^^^^^^^^^^^ (GenericTaggedUnion(u8))()
140+
141+
const generic_tagged_union_field = generic_tagged_union.field;
142+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^ (u8)()
143+
144+
const generic_tagged_union_tag = GenericTaggedUnion(u8).field;
145+
// ^^^^^^^^^^^^^^^^^^^^^^^^ (@typeInfo(GenericTaggedUnion(u8)).@"union".tag_type.?)()
146+
147+
fn GenericEnum(T: type) type {
148+
return enum {
149+
field,
150+
const decl: T = undefined;
151+
};
152+
}
153+
154+
const generic_enum_decl = GenericEnum(u8).decl;
155+
// ^^^^^^^^^^^^^^^^^ (u8)()
156+
157+
const generic_enum: GenericEnum(u8) = .field;
158+
// ^^^^^^^^^^^^ (GenericEnum(u8))()
159+
160+
const generic_enum_field = generic_enum.field;
161+
// ^^^^^^^^^^^^^^^^^^ (unknown)()
162+
163+
const generic_enum_tag = GenericEnum(u8).field;
164+
// ^^^^^^^^^^^^^^^^ (GenericEnum(u8))()
165+
166+
fn GenericStruct(T: type) type {
167+
return struct {
168+
field: T,
169+
const decl: T = undefined;
170+
};
171+
}
172+
173+
const generic_struct_decl = GenericStruct(u8).decl;
174+
// ^^^^^^^^^^^^^^^^^^^ (u8)()
175+
176+
const generic_struct: GenericStruct(u8) = .{ .field = 1 };
177+
// ^^^^^^^^^^^^^^ (GenericStruct(u8))()
178+
179+
const generic_struct_field = generic_struct.field;
180+
// ^^^^^^^^^^^^^^^^^^^^ (u8)()
181+
182+
const generic_struct_tag = GenericStruct(u8).field;
183+
// ^^^^^^^^^^^^^^^^^^ (unknown)()
184+
185+
fn Map(Context: type) type {
186+
return struct {
187+
unmanaged: MapUnmanaged(Context),
188+
ctx: Context,
189+
const Self = @This();
190+
fn clone(self: Self) Self {
191+
const unmanaged = self.unmanaged.cloneContext(self.ctx);
192+
// ^^^^^^^^^ (MapUnmanaged(either type))()
193+
return .{ .unmanaged = unmanaged, .ctx = self.ctx };
194+
}
195+
fn clone2(self: Self) Self {
196+
const unmanaged = self.unmanaged.cloneContext2(self.ctx);
197+
// ^^^^^^^^^ (MapUnmanaged(*either type))()
198+
return .{ .unmanaged = unmanaged, .ctx = self.ctx };
199+
}
200+
};
201+
}
202+
203+
fn MapUnmanaged(Context: type) type {
204+
return struct {
205+
size: u32,
206+
const Self = @This();
207+
fn clone(self: Self) Self {
208+
return self.cloneContext(@as(Context, undefined));
209+
}
210+
fn cloneContext(self: Self, new_ctx: anytype) MapUnmanaged(@TypeOf(new_ctx)) {
211+
_ = self;
212+
}
213+
fn clone2(self: Self) Self {
214+
return self.cloneContext2(@as(Context, undefined));
215+
}
216+
fn cloneContext2(self: Self, new_ctx: anytype) MapUnmanaged(*@TypeOf(new_ctx)) {
217+
_ = self;
218+
}
219+
};
220+
}
221+
222+
const some_list: std.ArrayListUnmanaged(u8) = .empty;
223+
// ^^^^^^^^^ (ArrayListAlignedUnmanaged(u8))()
224+
225+
const some_list_items = some_list.items;
226+
// ^^^^^^^^^^^^^^^ ([]u8)()
227+
228+
const std = @import("std");

0 commit comments

Comments
 (0)