@@ -285,28 +285,54 @@ impl SourceRootConfig {
285285 /// If a `SourceRoot` doesn't have a parent and is local then it is not contained in this mapping but it can be asserted that it is a root `SourceRoot`.
286286 pub fn source_root_parent_map ( & self ) -> FxHashMap < SourceRootId , SourceRootId > {
287287 let roots = self . fsc . roots ( ) ;
288- roots
289- . iter ( )
290- . enumerate ( )
291- . filter ( |( _, ( _, id) ) | self . local_filesets . contains ( id) )
292- . filter_map ( |( idx, ( root, root_id) ) | {
293- // We are interested in parents if they are also local source roots.
294- // So instead of a non-local parent we may take a local ancestor as a parent to a node.
295- //
296- // Here paths in roots are sorted lexicographically, so if a root
297- // is a parent of another root, it will be before it in the list.
298- roots[ ..idx] . iter ( ) . find_map ( |( root2, root2_id) | {
299- if self . local_filesets . contains ( root2_id)
300- && root. starts_with ( root2)
301- && root_id != root2_id
302- {
303- return Some ( ( root_id, root2_id) ) ;
288+
289+ let mut map = FxHashMap :: default ( ) ;
290+
291+ // See https://github.com/rust-lang/rust-analyzer/issues/17409
292+ //
293+ // We can view the connections between roots as a graph. The problem is
294+ // that this graph may contain cycles, so when adding edges, it is necessary
295+ // to check whether it will lead to a cycle.
296+ //
297+ // Since we ensure that each node has at most one outgoing edge (because
298+ // each SourceRoot can have only one parent), we can use a disjoint-set to
299+ // maintain the connectivity between nodes. If an edge’s two nodes belong
300+ // to the same set, they are already connected.
301+ let mut dsu = FxHashMap :: default ( ) ;
302+ fn find_parent ( dsu : & mut FxHashMap < u64 , u64 > , id : u64 ) -> u64 {
303+ if let Some ( & parent) = dsu. get ( & id) {
304+ let parent = find_parent ( dsu, parent) ;
305+ dsu. insert ( id, parent) ;
306+ parent
307+ } else {
308+ id
309+ }
310+ }
311+
312+ for ( idx, ( root, root_id) ) in roots. iter ( ) . enumerate ( ) {
313+ if !self . local_filesets . contains ( root_id)
314+ || map. contains_key ( & SourceRootId ( * root_id as u32 ) )
315+ {
316+ continue ;
317+ }
318+
319+ for ( root2, root2_id) in roots[ ..idx] . iter ( ) . rev ( ) {
320+ if self . local_filesets . contains ( root2_id)
321+ && root_id != root2_id
322+ && root. starts_with ( root2)
323+ {
324+ // check if the edge will create a cycle
325+ if find_parent ( & mut dsu, * root_id) != find_parent ( & mut dsu, * root2_id) {
326+ map. insert ( SourceRootId ( * root_id as u32 ) , SourceRootId ( * root2_id as u32 ) ) ;
327+ dsu. insert ( * root_id, * root2_id) ;
304328 }
305- None
306- } )
307- } )
308- . map ( |( & child, & parent) | ( SourceRootId ( child as u32 ) , SourceRootId ( parent as u32 ) ) )
309- . collect ( )
329+
330+ break ;
331+ }
332+ }
333+ }
334+
335+ map
310336 }
311337}
312338
@@ -592,4 +618,20 @@ mod tests {
592618
593619 assert_eq ! ( vc, vec![ ( SourceRootId ( 1 ) , SourceRootId ( 0 ) ) , ] )
594620 }
621+
622+ #[ test]
623+ fn circular_reference ( ) {
624+ let mut builder = FileSetConfigBuilder :: default ( ) ;
625+ builder. add_file_set ( vec ! [
626+ VfsPath :: new_virtual_path( "/ROOT/def" . to_owned( ) ) ,
627+ VfsPath :: new_virtual_path( "/ROOT/def/abc/def" . to_owned( ) ) ,
628+ ] ) ;
629+ builder. add_file_set ( vec ! [ VfsPath :: new_virtual_path( "/ROOT/def/abc" . to_owned( ) ) ] ) ;
630+ let fsc = builder. build ( ) ;
631+ let src = SourceRootConfig { fsc, local_filesets : vec ! [ 0 , 1 ] } ;
632+ let mut vc = src. source_root_parent_map ( ) . into_iter ( ) . collect :: < Vec < _ > > ( ) ;
633+ vc. sort_by ( |x, y| x. 0 . 0 . cmp ( & y. 0 . 0 ) ) ;
634+
635+ assert_eq ! ( vc, vec![ ( SourceRootId ( 1 ) , SourceRootId ( 0 ) ) , ] )
636+ }
595637}
0 commit comments