@@ -63,7 +63,7 @@ pub fn buildRule(options: zlinter.rules.RuleOptions) zlinter.rules.LintRule {
6363fn run (
6464 rule : zlinter.rules.LintRule ,
6565 doc : zlinter.session.LintDocument ,
66- allocator : std.mem.Allocator ,
66+ gpa : std.mem.Allocator ,
6767 options : zlinter.rules.RunOptions ,
6868) error {OutOfMemory }! ? zlinter.results.LintResult {
6969 const config = options .getConfig (Config );
@@ -72,41 +72,60 @@ fn run(
7272 const tree = doc .handle .tree ;
7373
7474 var problem_nodes = shims .ArrayList (Ast .Node .Index ).empty ;
75- defer problem_nodes .deinit (allocator );
75+ defer problem_nodes .deinit (gpa );
7676
7777 const root : NodeIndexShim = .root ;
78- var it = try doc .nodeLineageIterator (root , allocator );
78+ var it = try doc .nodeLineageIterator (
79+ root ,
80+ gpa ,
81+ );
7982 defer it .deinit ();
8083
84+ var arena : std.heap.ArenaAllocator = .init (gpa );
85+ defer arena .deinit ();
86+
8187 nodes : while (try it .next ()) | tuple | {
82- const node , _ = tuple ;
88+ const node = tuple [0 ].toNodeIndex ();
89+
90+ var buffer : [1 ]Ast.Node.Index = undefined ;
91+ const fn_decl = zlinter .ast .fnDecl (
92+ tree ,
93+ node ,
94+ & buffer ,
95+ ) orelse continue :nodes ;
8396
84- var fn_proto_buffer : [ 1 ] Ast.Node.Index = undefined ;
85- const fn_decl = zlinter . ast . fnDecl ( tree , node . toNodeIndex (), & fn_proto_buffer ) orelse continue :nodes ;
97+ if ( ! zlinter . ast . fnProtoReturnsError ( tree , fn_decl . proto ))
98+ continue :nodes ;
8699
87- if (! zlinter .ast .fnProtoReturnsError (tree , fn_decl .proto )) continue :nodes ;
100+ try processBlock (
101+ doc ,
102+ fn_decl .block ,
103+ & problem_nodes ,
104+ gpa ,
105+ arena .allocator (),
106+ );
88107
89- try processBlock ( doc , fn_decl . block , & problem_nodes , allocator );
108+ _ = arena . reset ( .retain_capacity );
90109 }
91110
92111 var lint_problems : shims .ArrayList (zlinter .results .LintProblem ) = .empty ;
93- defer lint_problems .deinit (allocator );
112+ defer lint_problems .deinit (gpa );
94113
95114 for (problem_nodes .items ) | node | {
96- try lint_problems .append (allocator , .{
115+ try lint_problems .append (gpa , .{
97116 .rule_id = rule .rule_id ,
98117 .severity = config .severity ,
99118 .start = .startOfNode (tree , node ),
100119 .end = .endOfNode (tree , node ),
101- .message = try std .fmt .allocPrint (allocator , "Missing `errdefer` cleanup" , .{}),
120+ .message = try std .fmt .allocPrint (gpa , "Missing `errdefer` cleanup" , .{}),
102121 });
103122 }
104123
105124 return if (lint_problems .items .len > 0 )
106125 try zlinter .results .LintResult .init (
107- allocator ,
126+ gpa ,
108127 doc .path ,
109- try lint_problems .toOwnedSlice (allocator ),
128+ try lint_problems .toOwnedSlice (gpa ),
110129 )
111130 else
112131 null ;
@@ -117,40 +136,56 @@ fn processBlock(
117136 block_node : Ast.Node.Index ,
118137 problems : * shims .ArrayList (Ast.Node.Index ),
119138 gpa : std.mem.Allocator ,
139+ arena : std.mem.Allocator ,
120140) ! void {
121141 const tree = doc .handle .tree ;
122142
123- var cleanup_symbols : std .StringHashMap (Ast .Node .Index ) = .init (gpa );
124- defer {
125- var it = cleanup_symbols .keyIterator ();
126- while (it .next ()) | k | gpa .free (k .* );
127- cleanup_symbols .deinit ();
128- }
143+ // Populated with declarations that look like they should be cleaned up.
144+ var cleanup_symbols : std .StringHashMap (Ast .Node .Index ) = .init (arena );
129145
130146 var call_buffer : [1 ]Ast.Node.Index = undefined ;
131147 for (doc .lineage .items (.children )[NodeIndexShim .init (block_node ).index ] orelse &.{}) | child_node | {
132148 if (try declRef (doc , child_node )) | decl_ref | {
133- if (decl_ref .hasDeinit ())
134- try cleanup_symbols .put (try gpa .dupe (u8 , decl_ref .var_name ), child_node );
135- } else if (try zlinter .ast .deferBlock (doc , child_node , gpa )) | defer_block | {
136- defer defer_block .deinit (gpa );
137-
149+ // Track declarations that look like they need to be cleaned up.
150+ if (! decl_ref .requiresCleanup ()) continue ;
151+
152+ try cleanup_symbols .put (
153+ try arena .dupe (u8 , decl_ref .var_name ),
154+ child_node ,
155+ );
156+ } else if (try zlinter .ast .deferBlock (
157+ doc ,
158+ child_node ,
159+ arena ,
160+ )) | defer_block | {
161+ // Remove any tracked declarations that are cleaned up within defer/errdefer
138162 for (defer_block .children ) | defer_block_child | {
139- const cleanup_call = callWithName (doc , defer_block_child , & call_buffer , &.{"deinit" }) orelse continue ;
140- switch (cleanup_call .kind ) {
163+ const call = callWithName (
164+ doc ,
165+ defer_block_child ,
166+ & call_buffer ,
167+ &.{"deinit" },
168+ ) orelse continue ;
169+ switch (call .kind ) {
141170 .single_field = > | info | {
142- if ( cleanup_symbols .fetchRemove (tree .tokenSlice (info .field_main_token ))) | e | gpa . free ( e . key );
171+ _ = cleanup_symbols .remove (tree .tokenSlice (info .field_main_token ));
143172 },
144173 .enum_literal , .other = > {},
145174 }
146175 }
147176 } else if (zlinter .ast .isBlock (tree , child_node )) {
148- try processBlock (doc , child_node , problems , gpa );
177+ try processBlock (
178+ doc ,
179+ child_node ,
180+ problems ,
181+ gpa ,
182+ arena ,
183+ );
149184 }
150185 }
151186
152- var leftover_it = cleanup_symbols .valueIterator ();
153- while (leftover_it .next ()) | node | {
187+ var remaining_it = cleanup_symbols .valueIterator ();
188+ while (remaining_it .next ()) | node | {
154189 try problems .append (gpa , node .* );
155190 }
156191}
@@ -270,6 +305,8 @@ const DeclRef = struct {
270305 name : []const u8 ,
271306 uri : []const u8 ,
272307
308+ // TODO: This really need a lot of work, it's just a quick hack to get
309+ // something going to see how useful such a rule is.
273310 const deinit_references = std .StaticStringMap ([]const u8 ).initComptime (.{
274311 .{ "ArrayHashMap" , "std/array_hash_map.zig" },
275312 .{ "ArrayHashMapUnmanaged" , "std/array_hash_map.zig" },
@@ -299,7 +336,7 @@ const DeclRef = struct {
299336 .{ "StringHashMapUnmanaged" , "std/hash_map.zig" },
300337 });
301338
302- fn hasDeinit (self : DeclRef ) bool {
339+ fn requiresCleanup (self : DeclRef ) bool {
303340 if (deinit_references .get (self .name )) | uri_suffix | {
304341 return std .mem .endsWith (u8 , self .uri , uri_suffix );
305342 }
@@ -308,21 +345,37 @@ const DeclRef = struct {
308345};
309346
310347fn declRef (doc : zlinter.session.LintDocument , var_decl_node : Ast.Node.Index ) ! ? DeclRef {
311- const var_decl = doc .handle .tree . fullVarDecl ( var_decl_node ) orelse return null ;
312-
313- const init_node = NodeIndexShim .initOptional (var_decl .ast .init_node ) orelse return null ;
348+ const tree = doc .handle .tree ;
349+ const var_decl = tree . fullVarDecl ( var_decl_node ) orelse return null ;
350+ const init_node = ( NodeIndexShim .initOptional (var_decl .ast .init_node ) orelse return null ). toNodeIndex () ;
314351
315352 var call_buffer : [1 ]Ast.Node.Index = undefined ;
316- if (! switch (shims .nodeTag (doc .handle .tree , init_node .toNodeIndex ())) {
317- .field_access = > zlinter .ast .isFieldVarAccess (doc .handle .tree , init_node .toNodeIndex (), &.{"empty" }),
318- .enum_literal = > zlinter .ast .isEnumLiteral (doc .handle .tree , init_node .toNodeIndex (), &.{"empty" }),
353+ if (! switch (shims .nodeTag (tree , init_node )) {
354+ // e.g., `ArrayList(u8).empty`
355+ .field_access = > zlinter .ast .isFieldVarAccess (
356+ tree ,
357+ init_node ,
358+ &.{"empty" },
359+ ),
360+ // e.g., `.empty`
361+ .enum_literal = > zlinter .ast .isEnumLiteral (
362+ tree ,
363+ init_node ,
364+ &.{"empty" },
365+ ),
319366 else = >
320367 // This will also handle optional and array accesses, which shouldn't occur
321368 // but shouldn't be a problem to us anyway as we do strict checks on the
322369 // type anyway.
323370 //
324- // e.g., `array[0].init()` and `optional.?.init()`
325- if (callWithName (doc , init_node .toNodeIndex (), & call_buffer , &.{"init" })) | call |
371+ // e.g., `array[0].init(gpa)` and `optional.?.init(allocator)`
372+ if (callWithName (
373+ doc ,
374+ init_node ,
375+ & call_buffer ,
376+ &.{"init" },
377+ )) | call |
378+ // TODO: This is fine for managed but what about unmanaged, which is now the standard?
326379 ! hasArenaParam (doc , call .params )
327380 else
328381 false ,
@@ -345,42 +398,42 @@ fn declRef(doc: zlinter.session.LintDocument, var_decl_node: Ast.Node.Index) !?D
345398 .container_decl_two ,
346399 .container_decl_two_trailing ,
347400 = > {
348- const tree = scope_handle .handle .tree ;
349- const first_token = tree .firstToken (node );
401+ const scope_tree = scope_handle .handle .tree ;
402+ const first_token = scope_tree .firstToken (node );
350403
351404 // `Foo = struct`
352- if (first_token > 1 and tree .tokens .items (.tag )[first_token - 2 ] == .identifier and tree .tokens .items (.tag )[first_token - 1 ] == .equal ) {
405+ if (first_token > 1 and scope_tree .tokens .items (.tag )[first_token - 2 ] == .identifier and scope_tree .tokens .items (.tag )[first_token - 1 ] == .equal ) {
353406 var str_token = first_token - 2 ;
354407 // `Foo: type = struct`
355- if (first_token > 3 and tree .tokens .items (.tag )[first_token - 4 ] == .identifier and tree .tokens .items (.tag )[first_token - 3 ] == .colon ) {
408+ if (first_token > 3 and scope_tree .tokens .items (.tag )[first_token - 4 ] == .identifier and scope_tree .tokens .items (.tag )[first_token - 3 ] == .colon ) {
356409 str_token = first_token - 4 ;
357410 }
358411 return .{
359- .var_name = doc . handle . tree .tokenSlice (var_decl .ast .mut_token + 1 ),
360- .name = tree .tokenSlice (str_token ),
412+ .var_name = tree .tokenSlice (var_decl .ast .mut_token + 1 ),
413+ .name = scope_tree .tokenSlice (str_token ),
361414 .uri = scope_handle .handle .uri ,
362415 };
363- } else if (first_token > 0 and tree .tokens .items (.tag )[first_token - 1 ] == .keyword_return ) {
416+ } else if (first_token > 0 and scope_tree .tokens .items (.tag )[first_token - 1 ] == .keyword_return ) {
364417 const doc_scope = try scope_handle .handle .getDocumentScope ();
365418
366419 const function_scope = switch (zlinter .version .zig ) {
367420 .@"0.14" = > zlinter .zls .Analyser .innermostFunctionScopeAtIndex (
368421 doc_scope ,
369- tree .tokens .items (.start )[first_token - 1 ],
422+ scope_tree .tokens .items (.start )[first_token - 1 ],
370423 ).unwrap (),
371424 .@"0.15" , .@"0.16" = > zlinter .zls .Analyser .innermostScopeAtIndexWithTag (
372425 doc_scope ,
373- tree .tokenStart (first_token - 1 ),
426+ scope_tree .tokenStart (first_token - 1 ),
374427 .initOne (.function ),
375428 ).unwrap (),
376429 } orelse return null ;
377430
378431 var fn_proto_buffer : [1 ]Ast.Node.Index = undefined ;
379- const func = tree .fullFnProto (& fn_proto_buffer , doc_scope .getScopeAstNode (function_scope ).? ).? ;
432+ const func = scope_tree .fullFnProto (& fn_proto_buffer , doc_scope .getScopeAstNode (function_scope ).? ).? ;
380433
381434 return .{
382- .var_name = doc . handle . tree .tokenSlice (var_decl .ast .mut_token + 1 ),
383- .name = tree .tokenSlice (func .name_token orelse return null ),
435+ .var_name = tree .tokenSlice (var_decl .ast .mut_token + 1 ),
436+ .name = scope_tree .tokenSlice (func .name_token orelse return null ),
384437 .uri = scope_handle .handle .uri ,
385438 };
386439 }
0 commit comments