-
-
Notifications
You must be signed in to change notification settings - Fork 446
Expand file tree
/
Copy pathanalysis.zig
More file actions
7019 lines (6287 loc) · 274 KB
/
Copy pathanalysis.zig
File metadata and controls
7019 lines (6287 loc) · 274 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//! The ZLS analysis backend.
//!
//! The most frequently used functions are:
//! - `resolveTypeOfNode`
//! - `getPositionContext`
//! - `lookupSymbolGlobal`
//! - `lookupSymbolContainer`
//!
const builtin = @import("builtin");
const std = @import("std");
const DocumentStore = @import("DocumentStore.zig");
const Ast = std.zig.Ast;
const offsets = @import("offsets.zig");
const Uri = @import("Uri.zig");
const log = std.log.scoped(.analysis);
const ast = @import("ast.zig");
const tracy = @import("tracy");
const InternPool = @import("analyser/InternPool.zig");
const ErrorMsg = @import("analyser/error_msg.zig").ErrorMsg;
const references = @import("features/references.zig");
pub const DocumentScope = @import("DocumentScope.zig");
pub const Declaration = DocumentScope.Declaration;
pub const Scope = DocumentScope.Scope;
const version_data = @import("version_data");
const Analyser = @This();
gpa: std.mem.Allocator,
arena: std.mem.Allocator,
store: *DocumentStore,
ip: *InternPool,
resolved_callsites: std.AutoHashMapUnmanaged(Declaration.Param, ?Type) = .empty,
resolved_nodes: std.HashMapUnmanaged(NodeWithUri, ?Binding, NodeWithUri.Context, std.hash_map.default_max_load_percentage) = .empty,
collect_callsite_references: bool,
/// avoid unnecessarily parsing number literals
resolve_number_literal_values: bool,
/// handle of the doc where the request originated
root_handle: ?*DocumentStore.Handle,
max_conditional_combos: usize = 200,
const NodeSet = std.HashMapUnmanaged(NodeWithUri, void, NodeWithUri.Context, std.hash_map.default_max_load_percentage);
pub const Error = std.mem.Allocator.Error || std.Io.Cancelable;
pub fn init(
gpa: std.mem.Allocator,
arena: std.mem.Allocator,
store: *DocumentStore,
ip: *InternPool,
root_handle: ?*DocumentStore.Handle,
) Analyser {
return .{
.gpa = gpa,
.arena = arena,
.store = store,
.ip = ip,
.collect_callsite_references = true,
.resolve_number_literal_values = false,
.root_handle = root_handle,
};
}
pub fn deinit(self: *Analyser) void {
self.resolved_callsites.deinit(self.gpa);
self.resolved_nodes.deinit(self.gpa);
}
fn allocType(analyser: *Analyser, ty: Type) error{OutOfMemory}!*Type {
const ptr = try analyser.arena.create(Type);
ptr.* = ty;
return ptr;
}
pub fn getDocCommentsBeforeToken(allocator: std.mem.Allocator, tree: *const Ast, base: Ast.TokenIndex) error{OutOfMemory}!?[]const u8 {
const doc_comment_index = getDocCommentTokenIndex(tree, base) orelse return null;
return try collectDocComments(allocator, tree, doc_comment_index, false);
}
/// Gets a declaration's doc comments. Caller owns returned memory.
pub fn getDocComments(allocator: std.mem.Allocator, tree: *const Ast, node: Ast.Node.Index) error{OutOfMemory}!?[]const u8 {
const base = tree.nodeMainToken(node);
const base_kind = tree.nodeTag(node);
switch (base_kind) {
.root => return try collectDocComments(allocator, tree, 0, true),
.fn_proto,
.fn_proto_one,
.fn_proto_simple,
.fn_proto_multi,
.fn_decl,
.local_var_decl,
.global_var_decl,
.aligned_var_decl,
.simple_var_decl,
.container_field_init,
.container_field_align,
.container_field,
=> return try getDocCommentsBeforeToken(allocator, tree, base),
else => {},
}
return null;
}
/// Get the first doc comment of a declaration.
pub fn getDocCommentTokenIndex(tree: *const Ast, base_token: Ast.TokenIndex) ?Ast.TokenIndex {
var idx = base_token;
if (idx == 0) return null;
idx -|= 1;
if (tree.tokenTag(idx) == .keyword_threadlocal and idx > 0) idx -|= 1;
if (tree.tokenTag(idx) == .string_literal and idx > 1 and tree.tokenTag(idx -| 1) == .keyword_extern) idx -|= 1;
if (tree.tokenTag(idx) == .keyword_extern and idx > 0) idx -|= 1;
if (tree.tokenTag(idx) == .keyword_export and idx > 0) idx -|= 1;
if (tree.tokenTag(idx) == .keyword_inline and idx > 0) idx -|= 1;
if (tree.tokenTag(idx) == .identifier and idx > 0) idx -|= 1;
if (tree.tokenTag(idx) == .keyword_pub and idx > 0) idx -|= 1;
// Find first doc comment token
if (!(tree.tokenTag(idx) == .doc_comment))
return null;
return while (tree.tokenTag(idx) == .doc_comment) {
if (idx == 0) break 0;
idx -|= 1;
} else idx + 1;
}
pub fn collectDocComments(allocator: std.mem.Allocator, tree: *const Ast, doc_comments: Ast.TokenIndex, container_doc: bool) error{OutOfMemory}![]const u8 {
var lines: std.ArrayList([]const u8) = .empty;
defer lines.deinit(allocator);
var lines_start_with_space = true;
var curr_line_tok = doc_comments;
while (true) : (curr_line_tok += 1) {
const comm = tree.tokenTag(curr_line_tok);
if ((container_doc and comm == .container_doc_comment) or (!container_doc and comm == .doc_comment)) {
const line = tree.tokenSlice(curr_line_tok)[3..];
if (line.len > 1 and line[0] != ' ') lines_start_with_space = false;
try lines.append(allocator, line);
} else break;
}
// If all of the lines that aren't empty start with a space, remove the first space
if (lines_start_with_space) {
for (lines.items, 0..) |line, i| {
if (line.len > 1 and line[0] == ' ') {
lines.items[i] = line[1..];
}
}
}
return try std.mem.join(allocator, "\n", lines.items);
}
/// Gets a function's keyword, name, arguments and return value.
pub fn getFunctionSignature(tree: *const Ast, func: Ast.full.FnProto) []const u8 {
const first_token = func.ast.fn_token;
const last_token = if (func.ast.return_type.unwrap()) |return_type| ast.lastToken(tree, return_type) else first_token;
return offsets.tokensToSlice(tree, first_token, last_token);
}
pub const FormatParameterOptions = struct {
referenced: ?*ReferencedType.Set = null,
info: Type.Data.Parameter,
include_modifier: bool,
include_name: bool,
include_type: bool,
};
pub fn stringifyParameter(analyser: *Analyser, options: FormatParameterOptions) error{OutOfMemory}![]u8 {
var aw: std.Io.Writer.Allocating = .init(analyser.arena);
defer aw.deinit();
analyser.rawStringifyParameter(&aw.writer, options) catch |err| switch (err) {
error.OutOfMemory, error.WriteFailed => return error.OutOfMemory,
};
return try aw.toOwnedSlice();
}
fn rawStringifyParameter(
analyser: *Analyser,
writer: *std.Io.Writer,
options: FormatParameterOptions,
) error{ OutOfMemory, WriteFailed }!void {
const referenced = options.referenced;
const info = options.info;
// Note that parameter doc comments are being skipped
if (options.include_modifier) {
if (info.modifier) |modifier| {
switch (modifier) {
.comptime_param => try writer.writeAll("comptime "),
.noalias_param => try writer.writeAll("noalias "),
}
}
}
if (options.include_name) {
if (info.name) |name| {
try writer.writeAll(name);
}
}
if (options.include_type) {
const has_parameter_name = options.include_name and info.name != null;
if (has_parameter_name) try writer.writeAll(": ");
try info.type.rawStringify(writer, analyser, .{
.referenced = referenced,
.truncate_container_decls = true,
});
}
}
pub const FormatFunctionOptions = struct {
referenced: ?*ReferencedType.Set = null,
info: Type.Data.Function,
include_fn_keyword: bool,
/// only included if available
include_name: bool,
override_name: ?[]const u8 = null,
skip_first_param: bool = false,
parameters: union(enum) {
collapse,
show: struct {
include_modifiers: bool,
include_names: bool,
include_types: bool,
},
},
include_return_type: bool,
snippet_placeholders: bool,
};
pub fn stringifyFunction(analyser: *Analyser, options: FormatFunctionOptions) error{OutOfMemory}![]u8 {
var aw: std.Io.Writer.Allocating = .init(analyser.arena);
defer aw.deinit();
analyser.rawStringifyFunction(&aw.writer, options) catch |err| switch (err) {
error.OutOfMemory, error.WriteFailed => return error.OutOfMemory,
};
return try aw.toOwnedSlice();
}
fn rawStringifyFunction(
analyser: *Analyser,
writer: *std.Io.Writer,
options: FormatFunctionOptions,
) error{ OutOfMemory, WriteFailed }!void {
const referenced = options.referenced;
const info = options.info;
var parameters = info.parameters;
var snippet_escaping_writer: SnippetEscapingWriter = .init(writer);
const escaping_writer = if (options.snippet_placeholders) &snippet_escaping_writer.interface else writer;
if (options.include_fn_keyword) {
try writer.writeAll("fn ");
}
if (options.include_name) no_name: {
const name = options.override_name orelse info.name orelse break :no_name;
try escaping_writer.writeAll(name);
}
try writer.writeByte('(');
if (options.skip_first_param) {
if (parameters.len >= 1) {
parameters = parameters[1..];
}
}
switch (options.parameters) {
.collapse => {
const has_arguments = parameters.len != 0;
if (has_arguments) {
if (options.snippet_placeholders) {
try writer.writeAll("${1:...}");
} else {
try writer.writeAll("...");
}
}
},
.show => |parameter_options| {
for (parameters, 0..) |param_info, index| {
if (index != 0) try writer.writeAll(", ");
if (options.snippet_placeholders) {
try writer.print("${{{d}:", .{index + 1});
}
try analyser.rawStringifyParameter(escaping_writer, .{
.referenced = referenced,
.info = param_info,
.include_modifier = parameter_options.include_modifiers,
.include_name = parameter_options.include_names,
.include_type = parameter_options.include_types,
});
if (options.snippet_placeholders) {
try writer.writeByte('}');
}
}
},
}
if (info.has_varargs) {
if (parameters.len != 0) {
try writer.writeAll(", ");
}
try writer.writeAll("...");
}
try writer.writeByte(')');
// ignoring align_expr
// ignoring addrspace_expr
// ignoring section_expr
// ignoring callconv_expr
if (options.include_return_type) {
try writer.writeByte(' ');
const return_type = try options.info.return_value.typeOf(analyser);
try return_type.rawStringify(escaping_writer, analyser, .{
.referenced = referenced,
.truncate_container_decls = true,
});
}
}
const SnippetEscapingWriter = struct {
out: *std.Io.Writer,
interface: std.Io.Writer,
pub fn init(writer: *std.Io.Writer) SnippetEscapingWriter {
return .{
.out = writer,
.interface = .{
.vtable = &.{
.drain = &drain,
.flush = std.Io.Writer.noopFlush,
.rebase = std.Io.Writer.failingRebase,
},
.buffer = &.{},
},
};
}
fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize {
const self: *SnippetEscapingWriter = @fieldParentPtr("interface", w);
const out = self.out;
std.debug.assert(w.buffer.len == 0);
for (data, 0..) |vec, i| {
const segment_index = std.mem.findAny(u8, vec, "$}\\") orelse continue;
if (i != 0) {
return try out.writeSplat(data[0..i], splat);
}
const segment = vec[0..segment_index];
const unescaped_char = vec[segment_index];
const bytes_written = try out.write(segment);
if (bytes_written < segment.len) return bytes_written;
try out.writeAll(&.{ '\\', unescaped_char });
return bytes_written + 1;
} else {
return try out.writeSplat(data, splat);
}
}
fn writeAll(raw_text: []const u8, writer: *std.Io.Writer) std.Io.Writer.Error!void {
var written: usize = 0;
for (raw_text, 0..) |c, i| {
switch (c) {
'$', '}', '\\' => {
try writer.writeAll(raw_text[written..i]);
try writer.writeAll(&.{ '\\', c });
written = i + 1;
},
else => continue,
}
}
try writer.writeAll(raw_text[written..]);
}
};
pub fn fmtEscapedSnippet(raw_text: []const u8) std.fmt.Alt([]const u8, SnippetEscapingWriter.writeAll) {
return .{ .data = raw_text };
}
pub fn renderBuiltinFunctionSignature(
arena: std.mem.Allocator,
name: []const u8,
builtin_data: version_data.Builtin,
multi_line: bool,
) error{OutOfMemory}![]u8 {
var signature: std.ArrayList(u8) = .empty;
try signature.appendSlice(arena, name);
try signature.append(arena, '(');
if (multi_line) try signature.append(arena, '\n');
for (builtin_data.parameters, 0..) |parameter, i| {
if (multi_line) {
try signature.appendSlice(arena, " ");
} else if (i != 0) {
try signature.appendSlice(arena, ", ");
}
try signature.appendSlice(arena, parameter.signature);
if (multi_line) {
try signature.appendSlice(arena, ",\n");
}
}
try signature.appendSlice(arena, ") ");
try signature.appendSlice(arena, builtin_data.return_type);
return signature.items;
}
pub fn isInstanceCall(
analyser: *Analyser,
call_handle: *DocumentStore.Handle,
call: Ast.full.Call,
func_ty: Type,
) Error!bool {
std.debug.assert(!func_ty.is_type_val);
if (call_handle.tree.nodeTag(call.ast.fn_expr) != .field_access) return false;
const container_node, _ = call_handle.tree.nodeData(call.ast.fn_expr).node_and_token;
const container_ty = if (try analyser.resolveTypeOfNodeInternal(.of(container_node, call_handle))) |container_instance|
try container_instance.typeOf(analyser)
else
func_ty.data.function.container_type.*;
std.debug.assert(container_ty.is_type_val);
return analyser.firstParamIs(func_ty, container_ty);
}
pub fn hasSelfParam(analyser: *Analyser, func_ty: Type) error{OutOfMemory}!bool {
std.debug.assert(func_ty.isFunc());
const container = func_ty.data.function.container_type.*;
if (container.is_type_val) return false;
const in_container = try container.typeOf(analyser);
if (in_container.isNamespace()) return false;
return analyser.firstParamIs(func_ty, in_container);
}
pub fn firstParamIs(
analyser: *Analyser,
func_type: Type,
expected_type: Type,
) bool {
_ = analyser;
std.debug.assert(expected_type.is_type_val);
std.debug.assert(func_type.isFunc());
const func_info = func_type.data.function;
if (func_info.parameters.len == 0) return false;
const resolved_type = func_info.parameters[0].type;
if (!resolved_type.is_type_val) return false;
if (resolved_type.data == .anytype_parameter) return true;
const deref_type = deref: switch (resolved_type.data) {
.pointer => |info| switch (info.size) {
.one => info.elem_ty.*,
.many, .slice, .c => return false,
},
.optional => |opt| switch (opt.data) {
.pointer => continue :deref opt.data,
else => opt.*,
},
else => resolved_type,
};
const deref_expected_type = switch (expected_type.data) {
.pointer => |info| switch (info.size) {
.one => info.elem_ty.*,
.many, .slice, .c => return false,
},
else => expected_type,
};
return switch (deref_type.data) {
.either => |entries| {
for (entries) |entry| {
if (entry.type_data.eql(deref_expected_type.data)) {
return true;
}
}
return false;
},
else => deref_type.eql(deref_expected_type),
};
}
pub fn getVariableSignature(
arena: std.mem.Allocator,
tree: *const Ast,
var_decl: Ast.full.VarDecl,
include_name: bool,
) error{OutOfMemory}![]const u8 {
const start_token = if (include_name)
var_decl.ast.mut_token
else if (var_decl.ast.type_node.unwrap()) |type_node|
tree.firstToken(type_node)
else if (var_decl.ast.init_node.unwrap()) |init_node|
tree.firstToken(init_node)
else
return "";
const init_node = var_decl.ast.init_node.unwrap() orelse {
const type_node = var_decl.ast.type_node.unwrap() orelse return "";
return offsets.tokensToSlice(tree, start_token, ast.lastToken(tree, type_node));
};
const end_token = switch (tree.nodeTag(init_node)) {
.container_decl,
.container_decl_trailing,
.container_decl_arg,
.container_decl_arg_trailing,
.container_decl_two,
.container_decl_two_trailing,
.tagged_union,
.tagged_union_trailing,
.tagged_union_enum_tag,
.tagged_union_enum_tag_trailing,
.tagged_union_two,
.tagged_union_two_trailing,
=> end_token: {
var buf: [2]Ast.Node.Index = undefined;
const container_decl = tree.fullContainerDecl(&buf, init_node).?;
var token = container_decl.ast.main_token;
var offset: Ast.TokenIndex = 0;
// Tagged union: union(enum)
if (container_decl.ast.enum_token) |enum_token| {
token = enum_token;
offset += 1;
}
// Backing integer: struct(u32), union(enum(u32))
// Tagged union: union(ComplexTypeTag)
if (container_decl.ast.arg.unwrap()) |arg| {
token = ast.lastToken(tree, arg);
offset += 1;
}
if (container_decl.ast.members.len == 0) break :end_token token + offset;
// e.g. 'pub const Mode = enum { zig, zon };'
if (tree.tokensOnSameLine(tree.firstToken(init_node), ast.lastToken(tree, init_node))) {
break :end_token ast.lastToken(tree, init_node);
}
var members_source: std.ArrayList(u8) = .empty;
for (container_decl.ast.members) |member| {
const member_line_start = offsets.lineLocUntilIndex(tree.source, tree.tokenStart(tree.firstToken(member))).start;
const member_source_indented = switch (tree.nodeTag(member)) {
.container_field_init,
.container_field_align,
.container_field,
=> tree.source[member_line_start..offsets.tokenToLoc(tree, ast.lastToken(tree, member)).end],
else => continue,
};
try members_source.append(arena, '\n');
try members_source.appendSlice(arena, try trimCommonIndentation(arena, member_source_indented, 4));
try members_source.append(arena, ',');
}
if (members_source.items.len == 0) break :end_token token + offset;
return try std.mem.concat(arena, u8, &.{
offsets.tokensToSlice(tree, start_token, token + offset),
" {",
members_source.items,
"\n}",
});
},
else => ast.lastToken(tree, init_node),
};
return offsets.tokensToSlice(tree, start_token, end_token);
}
fn trimCommonIndentation(allocator: std.mem.Allocator, str: []const u8, preserved_indentation_amount: usize) error{OutOfMemory}![]u8 {
var line_it = std.mem.splitScalar(u8, str, '\n');
var non_empty_lines: usize = 0;
var min_indentation: ?usize = null;
while (line_it.next()) |line| {
if (line.len == 0) continue;
const indentation = for (line, 0..) |c, count| {
if (!std.ascii.isWhitespace(c)) break count;
} else line.len;
min_indentation = if (min_indentation) |old| @min(old, indentation) else indentation;
non_empty_lines += 1;
}
var common_indent = min_indentation orelse return try allocator.dupe(u8, str);
common_indent -|= preserved_indentation_amount;
if (common_indent == 0) return try allocator.dupe(u8, str);
const capacity = str.len - non_empty_lines * common_indent;
var output: std.ArrayList(u8) = try .initCapacity(allocator, capacity);
std.debug.assert(capacity == output.capacity);
errdefer @compileError("error would leak here");
line_it = std.mem.splitScalar(u8, str, '\n');
var is_first_line = true;
while (line_it.next()) |line| {
if (!is_first_line) output.appendAssumeCapacity('\n');
if (line.len != 0) {
output.appendSliceAssumeCapacity(line[common_indent..]);
}
is_first_line = false;
}
std.debug.assert(output.items.len == output.capacity);
return output.items;
}
test trimCommonIndentation {
const cases = [_]struct { []const u8, []const u8, usize }{
.{ "", "", 0 },
.{ "\n", "\n", 0 },
.{ "foo", "foo", 0 },
.{ "foo", " foo", 0 },
.{ "foo ", " foo ", 0 },
.{ "foo\nbar", " foo\n bar", 0 },
.{ "foo\nbar\n", " foo\n bar\n", 0 },
.{ " foo\nbar", " foo\n bar", 0 },
.{ "foo\n bar", " foo\n bar", 0 },
.{ " foo\n\nbar", " foo\n\n bar", 0 },
.{ " foo\n bar", " foo\n bar", 2 },
.{ " foo\n bar", " foo\n bar", 4 },
.{ " foo\n bar", " foo\n bar", 8 },
};
for (cases) |case| {
const actual = try trimCommonIndentation(std.testing.allocator, case[1], case[2]);
defer std.testing.allocator.free(actual);
try std.testing.expectEqualStrings(case[0], actual);
}
}
/// Returns whether the given `node` is the identifier `type`.
pub fn isMetaType(tree: *const Ast, node: Ast.Node.Index) bool {
if (tree.nodeTag(node) == .identifier) {
return std.mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(node)), "type");
}
return false;
}
/// Returns whether the given function returns a `type`.
pub fn isTypeFunction(tree: *const Ast, func: Ast.full.FnProto) bool {
const return_type = func.ast.return_type.unwrap() orelse return false;
return isMetaType(tree, return_type);
}
// ANALYSIS ENGINE
/// Resolves variable declarations consisting of chains of imports and field accesses of containers
/// Examples:
///```zig
/// const decl = @import("decl-file.zig").decl;
/// const other = decl.middle.other;
///```
pub fn resolveVarDeclAlias(analyser: *Analyser, decl: DeclWithHandle) Error!?DeclWithHandle {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
const initial_node = switch (decl.decl) {
.ast_node => |node| node,
else => return null,
};
var node_trail: NodeSet = .empty;
defer node_trail.deinit(analyser.gpa);
var current: ResolveOptions = .{
.node_handle = .of(initial_node, decl.handle),
.container_type = decl.container_type,
};
var result: ?DeclWithHandle = null;
while (true) {
const node = current.node_handle.node;
const handle = current.node_handle.handle;
const tree = &handle.tree;
const resolved: DeclWithHandle = switch (tree.nodeTag(node)) {
.identifier => blk: {
const name_token = ast.identifierTokenFromIdentifierNode(tree, node) orelse break :blk null;
const name = offsets.identifierTokenToNameSlice(tree, name_token);
if (current.container_type) |ty| {
break :blk try ty.lookupSymbol(analyser, name);
}
break :blk try analyser.lookupSymbolGlobal(
handle,
name,
tree.tokenStart(name_token),
);
},
.field_access => blk: {
const lhs, const field_name = tree.nodeData(node).node_and_token;
const resolved = (try analyser.resolveTypeOfNode(.{
.node_handle = .of(lhs, handle),
.container_type = current.container_type,
})) orelse break :blk null;
if (!resolved.is_type_val)
break :blk null;
const symbol_name = offsets.identifierTokenToNameSlice(tree, field_name);
break :blk try resolved.lookupSymbol(analyser, symbol_name);
},
.global_var_decl,
.local_var_decl,
.aligned_var_decl,
.simple_var_decl,
=> {
const var_decl = tree.fullVarDecl(node).?;
const base_exp = var_decl.ast.init_node.unwrap() orelse return result;
if (tree.tokenTag(var_decl.ast.mut_token) != .keyword_const) return result;
const gop = try node_trail.getOrPut(analyser.gpa, .{ .node = base_exp, .uri = handle.uri });
if (gop.found_existing) return null;
current.node_handle.node = base_exp;
continue;
},
else => null,
} orelse return result;
const resolved_node = switch (resolved.decl) {
.ast_node => |resolved_node| resolved_node,
else => return resolved,
};
const gop = try node_trail.getOrPut(analyser.gpa, .{ .node = resolved_node, .uri = resolved.handle.uri });
if (gop.found_existing) return null;
current = .{
.node_handle = .of(resolved_node, resolved.handle),
.container_type = resolved.container_type,
};
result = resolved;
}
}
/// resolves `@field(lhs, field_name)`
pub fn resolveFieldAccess(analyser: *Analyser, lhs: Type, field_name: []const u8) Error!?Type {
const binding = try analyser.resolveFieldAccessBinding(.{ .type = lhs, .is_const = false }, field_name) orelse return null;
return binding.type;
}
pub fn resolveFieldAccessBinding(analyser: *Analyser, lhs_binding: Binding, field_name: []const u8) Error!?Binding {
const lhs = lhs_binding.type;
if (try analyser.resolveUnionTagAccess(lhs, field_name)) |t|
return .{ .type = t, .is_const = true };
// If we are accessing a pointer type, remove one pointerness level :)
const left_type = (try analyser.resolveDerefType(lhs)) orelse lhs;
if (try analyser.resolvePropertyType(left_type, field_name)) |t|
return .{
.type = t,
.is_const = lhs_binding.is_const,
};
if (try left_type.lookupSymbol(analyser, field_name)) |child|
return .{
.type = try child.resolveType(analyser) orelse return null,
.is_const = if (left_type.is_type_val) child.isConst() else lhs_binding.is_const,
};
return null;
}
pub fn resolveGenericType(analyser: *Analyser, ty: Type, bound_params: TokenToTypeMap) error{OutOfMemory}!Type {
var visiting: Type.Data.GenericSet = .empty;
defer visiting.deinit(analyser.gpa);
return analyser.resolveGenericTypeInternal(ty, bound_params, &visiting);
}
fn resolveGenericTypeInternal(
analyser: *Analyser,
ty: Type,
bound_params: TokenToTypeMap,
visiting: *Type.Data.GenericSet,
) error{OutOfMemory}!Type {
var resolved = ty;
if (!ty.is_type_val) {
resolved = try resolved.typeOf(analyser);
}
std.debug.assert(resolved.is_type_val);
resolved.data = try resolved.data.resolveGeneric(analyser, bound_params, visiting);
if (!ty.is_type_val) {
resolved = try resolved.instanceUnchecked(analyser);
}
return resolved;
}
fn findReturnStatementInternal(tree: *const Ast, body: Ast.Node.Index, already_found: *bool) ?Ast.Node.Index {
var result: ?Ast.Node.Index = null;
var buffer: [2]Ast.Node.Index = undefined;
const statements = tree.blockStatements(&buffer, body) orelse return null;
for (statements) |child_idx| {
if (tree.nodeTag(child_idx) == .@"return") {
if (already_found.*) return null;
already_found.* = true;
result = child_idx;
continue;
}
result = findReturnStatementInternal(tree, child_idx, already_found);
}
return result;
}
fn findReturnStatement(tree: *const Ast, body: Ast.Node.Index) ?Ast.Node.Index {
var already_found = false;
return findReturnStatementInternal(tree, body, &already_found);
}
/// if `func_type_param` is callable, returns an instance of the return type.
/// otherwise, returns null.
pub fn resolveReturnType(analyser: *Analyser, func_type_param: Type) error{OutOfMemory}!?Type {
const func_type = try analyser.resolveFuncProtoOfCallable(func_type_param) orelse return null;
const info = func_type.data.function;
return info.return_value.*;
}
fn resolveReturnValueOfFuncNode(
analyser: *Analyser,
handle: *DocumentStore.Handle,
func_node: Ast.Node.Index,
) Error!?Type {
const tree = &handle.tree;
var buf: [1]Ast.Node.Index = undefined;
const fn_proto = tree.fullFnProto(&buf, func_node).?;
const has_body = tree.nodeTag(func_node) == .fn_decl;
if (isTypeFunction(tree, fn_proto)) {
if (!has_body) return .unknown_type;
const body = tree.nodeData(func_node).node_and_node[1];
// If this is a type function and it only contains a single return statement that returns
// a container declaration, we will return that declaration.
const return_node = findReturnStatement(tree, body) orelse return .unknown_type;
if (tree.nodeData(return_node).opt_node.unwrap()) |return_expr| {
return try analyser.resolveTypeOfNodeInternal(.of(return_expr, handle)) orelse .unknown_type;
}
return .unknown_type;
}
const return_type = fn_proto.ast.return_type.unwrap() orelse return null;
const child_type = (try analyser.resolveTypeOfNodeInternal(.of(return_type, handle))) orelse
return null;
if (!child_type.is_type_val) return null;
if (ast.hasInferredError(tree, fn_proto)) {
const ty = try Type.createErrorUnionType(analyser, null, child_type);
return try ty.instanceUnchecked(analyser);
}
return try child_type.instanceTypeVal(analyser);
}
/// `optional.?`
pub fn resolveOptionalUnwrap(analyser: *Analyser, optional: Type) error{OutOfMemory}!?Type {
if (optional.is_type_val) return null;
// TODO: some uses of this function don't expect C pointers to be unwrapped
switch (optional.data) {
.optional => |child_ty| return try child_ty.instanceUnchecked(analyser),
.pointer => |ptr| {
if (ptr.size == .c) return optional;
return null;
},
.ip_index => |payload| switch (analyser.ip.indexToKey(payload.type)) {
.optional_type => |optional_info| return Type.fromIP(analyser, optional_info.payload_type, null),
.pointer_type => |pointer_info| {
if (pointer_info.flags.size == .c) return optional;
return null;
},
else => return null,
},
else => return null,
}
}
pub fn resolveOrelseType(analyser: *Analyser, lhs: Type, rhs: Type) error{OutOfMemory}!?Type {
if (rhs.is_type_val) return null;
return switch (rhs.data) {
.optional => rhs,
.ip_index => |payload| switch (analyser.ip.indexToKey(payload.type)) {
.optional_type => rhs,
else => try analyser.resolveOptionalUnwrap(lhs),
},
else => try analyser.resolveOptionalUnwrap(lhs),
};
}
pub fn resolveAddressOf(analyser: *Analyser, is_const: bool, ty: Type) error{OutOfMemory}!Type {
const elem_ty = try ty.typeOf(analyser);
const pointer_ty = try Type.createPointerType(analyser, .one, .none, is_const, elem_ty);
return try pointer_ty.instanceUnchecked(analyser);
}
pub const ErrorUnionSide = enum { error_set, payload };
pub fn resolveUnwrapErrorUnionType(analyser: *Analyser, ty: Type, side: ErrorUnionSide) error{OutOfMemory}!?Type {
if (ty.is_type_val) return null;
return switch (ty.data) {
.error_union => |info| switch (side) {
.error_set => try (info.error_set orelse return null).instanceTypeVal(analyser),
.payload => try info.payload.instanceTypeVal(analyser),
},
.ip_index => |payload| switch (analyser.ip.indexToKey(payload.type)) {
.error_union_type => |error_union_info| switch (side) {
.error_set => {
if (error_union_info.error_set_type == .none) return null;
return Type.fromIP(analyser, error_union_info.error_set_type, null);
},
.payload => return Type.fromIP(analyser, error_union_info.payload_type, null),
},
else => return null,
},
else => return null,
};
}
fn resolveUnionTag(analyser: *Analyser, ty: Type) Error!?Type {
if (!ty.is_type_val)
return null;
if (!ty.isTaggedUnion())
return null;
const scope_handle = switch (ty.data) {
.container => |info| info.scope_handle,
else => return null,
};
const node = scope_handle.toNode();
const handle = scope_handle.handle;
var buf: [2]Ast.Node.Index = undefined;
const container_decl = handle.tree.fullContainerDecl(&buf, node) orelse
return null;
if (container_decl.ast.enum_token != null)
return .{ .data = .{ .union_tag = try analyser.allocType(ty) }, .is_type_val = false };
if (container_decl.ast.arg.unwrap()) |arg| {
const tag_type = (try analyser.resolveTypeOfNode(.of(arg, handle))) orelse return null;
return try tag_type.instanceTypeVal(analyser) orelse return null;
}
return null;
}
fn resolveUnionTagAccess(analyser: *Analyser, ty: Type, symbol: []const u8) Error!?Type {
if (!ty.is_type_val)
return null;
if (!ty.isTaggedUnion())
return null;
const child = try ty.lookupSymbol(analyser, symbol) orelse
return null;
if (child.decl != .ast_node or !child.handle.tree.nodeTag(child.decl.ast_node).isContainerField())
return null;
return try analyser.resolveUnionTag(ty);
}
pub fn resolveFuncProtoOfCallable(analyser: *Analyser, ty: Type) error{OutOfMemory}!?Type {
const deref_type = try analyser.resolveDerefType(ty) orelse ty;
if (!deref_type.isFunc()) return null;
return deref_type;
}
/// resolve a pointer dereference
/// `pointer.*`
pub fn resolveDerefType(analyser: *Analyser, pointer: Type) error{OutOfMemory}!?Type {
const binding = try analyser.resolveDerefBinding(pointer) orelse return null;
return binding.type;
}