@@ -289,6 +289,158 @@ fn symbolReferences(
289289 return builder .locations ;
290290}
291291
292+ const ControlFlowBuilder = struct {
293+ const Error = error {OutOfMemory };
294+ locations : std .ArrayListUnmanaged (types .Location ) = .empty ,
295+ encoding : offsets.Encoding ,
296+ token_handle : Analyser.TokenWithHandle ,
297+ allocator : std.mem.Allocator ,
298+ label : ? []const u8 = null ,
299+ last_loop : Ast.TokenIndex ,
300+ nodes : []const Ast.Node.Index ,
301+ fn iter (builder : * ControlFlowBuilder , tree : Ast , node : Ast.Node.Index ) Error ! void {
302+ const main_token = tree .nodeMainToken (node );
303+ switch (tree .nodeTag (node )) {
304+ .@"break" , .@"continue" = > {
305+ if (tree .nodeData (node ).opt_token_and_opt_node [0 ].unwrap ()) | label_token | {
306+ const loop_or_switch_label = builder .label orelse return ;
307+ const label = offsets .identifierTokenToNameSlice (tree , label_token );
308+ if (std .mem .eql (u8 , loop_or_switch_label , label )) {
309+ try builder .add (main_token );
310+ }
311+ } else for (builder .nodes ) | n | switch (tree .nodeTag (n )) {
312+ .for_simple ,
313+ .@"for" ,
314+ .while_cont ,
315+ .while_simple ,
316+ .@"while" ,
317+ = > if (tree .nodeMainToken (n ) == builder .last_loop )
318+ try builder .add (main_token ),
319+ // break/continue on a switch must be labeled
320+ .@"switch" ,
321+ .switch_comma ,
322+ = > {},
323+ else = > {},
324+ };
325+ },
326+
327+ .@"while" ,
328+ .while_simple ,
329+ .while_cont ,
330+ .@"for" ,
331+ .for_simple ,
332+ = > {
333+ const last_loop = builder .last_loop ;
334+ defer builder .last_loop = last_loop ;
335+ builder .last_loop = main_token ;
336+ try ast .iterateChildren (tree , node , builder , Error , iter );
337+ },
338+ else = > try ast .iterateChildren (tree , node , builder , Error , iter ),
339+ }
340+ }
341+
342+ fn add (builder : * ControlFlowBuilder , token_index : Ast.TokenIndex ) Error ! void {
343+ const handle = builder .token_handle .handle ;
344+ try builder .locations .append (builder .allocator , .{
345+ .uri = handle .uri ,
346+ .range = offsets .tokenToRange (handle .tree , token_index , builder .encoding ),
347+ });
348+ }
349+
350+ fn deinit (builder : * ControlFlowBuilder ) void {
351+ builder .locations .deinit (builder .allocator );
352+ }
353+ };
354+
355+ fn controlFlowReferences (
356+ allocator : std.mem.Allocator ,
357+ token_handle : Analyser.TokenWithHandle ,
358+ encoding : offsets.Encoding ,
359+ include_decl : bool ,
360+ ) error {OutOfMemory }! std .ArrayListUnmanaged (types .Location ) {
361+ const handle = token_handle .handle ;
362+ const tree = handle .tree ;
363+ const kw_token = token_handle .token ;
364+
365+ const source_index = handle .tree .tokenStart (kw_token );
366+ const nodes = try ast .nodesOverlappingIndex (allocator , tree , source_index );
367+ defer allocator .free (nodes );
368+
369+ var builder : ControlFlowBuilder = .{
370+ .allocator = allocator ,
371+ .token_handle = token_handle ,
372+ .encoding = encoding ,
373+ .last_loop = kw_token ,
374+ .nodes = nodes ,
375+ };
376+ defer builder .deinit ();
377+
378+ if (include_decl ) {
379+ try builder .add (kw_token );
380+ }
381+
382+ switch (tree .tokenTag (kw_token )) {
383+ .keyword_continue ,
384+ .keyword_break ,
385+ = > {
386+ const maybe_label = blk : {
387+ if (kw_token + 2 >= tree .tokens .len ) break :blk null ;
388+ if (tree .tokenTag (kw_token + 1 ) != .colon ) break :blk null ;
389+ if (tree .tokenTag (kw_token + 2 ) != .identifier ) break :blk null ;
390+ break :blk offsets .identifierTokenToNameSlice (tree , kw_token + 2 );
391+ };
392+ for (nodes ) | node | switch (tree .nodeTag (node )) {
393+ .for_simple ,
394+ .@"for" ,
395+ .while_cont ,
396+ .while_simple ,
397+ .@"while" ,
398+ = > {
399+ // if the break/continue is unlabeled it must belong to the first loop we encounter
400+ const main_token = tree .nodeMainToken (node );
401+ const label = maybe_label orelse break try builder .add (main_token );
402+ const loop_label = if (tree .isTokenPrecededByTags (main_token , &.{ .identifier , .colon }))
403+ offsets .identifierTokenToNameSlice (tree , main_token - 2 )
404+ else
405+ continue ;
406+ if (std .mem .eql (u8 , label , loop_label )) {
407+ try builder .add (main_token );
408+ }
409+ },
410+ .switch_comma ,
411+ .@"switch" ,
412+ = > {
413+ const label = maybe_label orelse continue ;
414+ const main_token = tree .nodeMainToken (node );
415+ const switch_label = if (tree .tokenTag (main_token ) == .identifier )
416+ offsets .identifierTokenToNameSlice (tree , main_token )
417+ else
418+ continue ;
419+ if (std .mem .eql (u8 , label , switch_label )) {
420+ try builder .add (
421+ // we already know the switch is labeled so we can just offset
422+ main_token + 2 ,
423+ );
424+ }
425+ },
426+ else = > {},
427+ };
428+ },
429+ .keyword_for ,
430+ .keyword_while ,
431+ .keyword_switch ,
432+ = > {
433+ if (tree .isTokenPrecededByTags (kw_token , &.{ .identifier , .colon }))
434+ builder .label = tree .tokenSlice (kw_token - 2 );
435+ try ast .iterateChildren (tree , nodes [0 ], & builder , ControlFlowBuilder .Error , ControlFlowBuilder .iter );
436+ },
437+ else = > {},
438+ }
439+
440+ defer builder .locations = .empty ;
441+ return builder .locations ;
442+ }
443+
292444pub const Callsite = struct {
293445 uri : []const u8 ,
294446 call_node : Ast.Node.Index ,
@@ -446,47 +598,60 @@ pub fn referencesHandler(server: *Server, arena: std.mem.Allocator, request: Gen
446598 if (handle .tree .mode == .zon ) return null ;
447599
448600 const source_index = offsets .positionToIndex (handle .tree .source , request .position (), server .offset_encoding );
449- const name_loc = Analyser .identifierLocFromIndex (handle .tree , source_index ) orelse return null ;
450- const name = offsets .locToSlice (handle .tree .source , name_loc );
451601 const pos_context = try Analyser .getPositionContext (server .allocator , handle .tree , source_index , true );
452602
453603 var analyser = server .initAnalyser (arena , handle );
454604 defer analyser .deinit ();
455605
456- // TODO: Make this work with branching types
457- const decl = switch (pos_context ) {
458- .var_access = > try analyser .lookupSymbolGlobal (handle , name , source_index ),
459- .field_access = > | loc | z : {
460- const held_loc = offsets .locMerge (loc , name_loc );
461- const a = try analyser .getSymbolFieldAccesses (arena , handle , source_index , held_loc , name );
462- if (a ) | b | {
463- if (b .len != 0 ) break :z b [0 ];
464- }
465-
466- break :z null ;
467- },
468- .label_access , .label_decl = > try Analyser .lookupLabel (handle , name , source_index ),
469- .enum_literal = > try analyser .getSymbolEnumLiteral (handle , source_index , name ),
470- else = > null ,
471- } orelse return null ;
472-
473606 const include_decl = switch (request ) {
474607 .references = > | ref | ref .context .includeDeclaration ,
475608 else = > true ,
476609 };
477610
478- const locations = if (decl .decl == .label )
479- try labelReferences (arena , decl , server .offset_encoding , include_decl )
480- else
481- try symbolReferences (
482- arena ,
483- & analyser ,
484- request ,
485- decl ,
486- server .offset_encoding ,
487- include_decl ,
488- server .config .skip_std_references ,
489- );
611+ // TODO: Make this work with branching types
612+ const locations = locs : {
613+ if (pos_context == .keyword and request != .rename ) {
614+ break :locs try controlFlowReferences (
615+ arena ,
616+ .{ .token = offsets .sourceIndexToTokenIndex (handle .tree , source_index ).preferLeft (), .handle = handle },
617+ server .offset_encoding ,
618+ include_decl ,
619+ );
620+ }
621+
622+ const name_loc = Analyser .identifierLocFromIndex (handle .tree , source_index ) orelse return null ;
623+ const name = offsets .locToSlice (handle .tree .source , name_loc );
624+
625+ const decl = switch (pos_context ) {
626+ .var_access = > try analyser .lookupSymbolGlobal (handle , name , source_index ),
627+ .field_access = > | loc | z : {
628+ const held_loc = offsets .locMerge (loc , name_loc );
629+ const a = try analyser .getSymbolFieldAccesses (arena , handle , source_index , held_loc , name );
630+ if (a ) | b | {
631+ if (b .len != 0 ) break :z b [0 ];
632+ }
633+
634+ break :z null ;
635+ },
636+ .label_access , .label_decl = > try Analyser .lookupLabel (handle , name , source_index ),
637+ .enum_literal = > try analyser .getSymbolEnumLiteral (handle , source_index , name ),
638+ .keyword = > null ,
639+ else = > null ,
640+ } orelse return null ;
641+
642+ break :locs switch (decl .decl ) {
643+ .label = > try labelReferences (arena , decl , server .offset_encoding , include_decl ),
644+ else = > try symbolReferences (
645+ arena ,
646+ & analyser ,
647+ request ,
648+ decl ,
649+ server .offset_encoding ,
650+ include_decl ,
651+ server .config .skip_std_references ,
652+ ),
653+ };
654+ };
490655
491656 switch (request ) {
492657 .rename = > | rename | {
0 commit comments