@@ -223,6 +223,8 @@ pub const Handle = struct {
223223 self .* = undefined ;
224224 }
225225 } = .init ,
226+
227+ associated_compilation_units : GetAssociatedCompilationUnitsResult = .unresolved ,
226228 },
227229
228230 const ZirOrZoir = union (Ast .Mode ) {
@@ -302,6 +304,7 @@ pub const Handle = struct {
302304 self .cimports .deinit (allocator );
303305
304306 self .impl .associated_build_file .deinit (allocator );
307+ self .impl .associated_compilation_units .deinit (allocator );
305308
306309 self .* = undefined ;
307310 }
@@ -508,6 +511,93 @@ pub const Handle = struct {
508511 return .none ;
509512 }
510513
514+ pub const GetAssociatedCompilationUnitsResult = union (enum ) {
515+ /// The Handle has no associated compilation unit.
516+ none ,
517+ /// The associated compilation unit has not been resolved yet.
518+ unresolved ,
519+ /// The associated compilation unit has been successfully resolved to a list of root module.
520+ resolved : []const []const u8 ,
521+
522+ fn deinit (result : * GetAssociatedCompilationUnitsResult , allocator : std.mem.Allocator ) void {
523+ switch (result .* ) {
524+ .none , .unresolved = > {},
525+ .resolved = > | root_source_files | {
526+ allocator .free (root_source_files );
527+ },
528+ }
529+ result .* = undefined ;
530+ }
531+ };
532+
533+ /// Returns the root source file of the root module of the given handle. Same as `@import("root")`.
534+ pub fn getAssociatedCompilationUnits (self : * Handle , document_store : * DocumentStore ) error {OutOfMemory }! GetAssociatedCompilationUnitsResult {
535+ const build_file , const target_root_source_file = switch (self .impl .associated_compilation_units ) {
536+ else = > return self .impl .associated_compilation_units ,
537+ .unresolved = > switch (try self .getAssociatedBuildFile (document_store )) {
538+ .none = > return .none ,
539+ .unresolved = > return .unresolved ,
540+ .resolved = > | resolved | .{ resolved .build_file , resolved .root_source_file },
541+ },
542+ };
543+
544+ const build_config = build_file .tryLockConfig () orelse return .none ;
545+ defer build_file .unlockConfig ();
546+
547+ const allocator = document_store .allocator ;
548+ const modules = & build_config .modules .map ;
549+
550+ var visted : std.DynamicBitSetUnmanaged = try .initEmpty (allocator , modules .count ());
551+ defer visted .deinit (allocator );
552+
553+ var queue : std .ArrayList (usize ) = try .initCapacity (allocator , 1 );
554+ defer queue .deinit (allocator );
555+
556+ const target_index = modules .getIndex (target_root_source_file ).? ;
557+
558+ // We only care about the root source file of each root module so we convert them to a set.
559+ var root_modules : std .StringArrayHashMapUnmanaged (void ) = .empty ;
560+ defer root_modules .deinit (allocator );
561+
562+ try root_modules .ensureTotalCapacity (allocator , build_config .compilations .len );
563+ for (build_config .compilations ) | compile | {
564+ root_modules .putAssumeCapacity (compile .root_module , {});
565+ }
566+
567+ var results : std .ArrayList ([]const u8 ) = .empty ;
568+ defer results .deinit (allocator );
569+
570+ // Do a graph search from root modules until we reach `root_source_file`
571+ for (root_modules .keys ()) | root_module | {
572+ visted .unsetAll ();
573+ queue .clearRetainingCapacity ();
574+ queue .appendAssumeCapacity (modules .getIndex (root_module ).? );
575+
576+ while (queue .pop ()) | index | {
577+ if (index == target_index ) {
578+ try results .append (allocator , root_module );
579+ break ;
580+ }
581+
582+ if (visted .isSet (index )) continue ;
583+ visted .set (index );
584+
585+ const imported_modules = modules .values ()[index ].import_table .map .values ();
586+ try queue .ensureUnusedCapacity (allocator , imported_modules .len );
587+ for (imported_modules ) | root_source_file | {
588+ queue .appendAssumeCapacity (modules .getIndex (root_source_file ) orelse continue );
589+ }
590+ }
591+ }
592+
593+ if (results .items .len == 0 ) {
594+ self .impl .associated_compilation_units = .none ;
595+ } else {
596+ self .impl .associated_compilation_units = .{ .resolved = try results .toOwnedSlice (allocator ) };
597+ }
598+ return self .impl .associated_compilation_units ;
599+ }
600+
511601 fn getLazy (
512602 self : * Handle ,
513603 comptime T : type ,
@@ -1346,10 +1436,14 @@ fn createAndStoreDocument(
13461436
13471437 if (gop .found_existing ) {
13481438 std .debug .assert (new_handle .impl .associated_build_file == .init );
1439+ std .debug .assert (new_handle .impl .associated_compilation_units == .unresolved );
13491440 if (lsp_synced ) {
13501441 new_handle .impl .associated_build_file = gop .value_ptr .* .impl .associated_build_file ;
13511442 gop .value_ptr .* .impl .associated_build_file = .init ;
13521443
1444+ new_handle .impl .associated_compilation_units = gop .value_ptr .* .impl .associated_compilation_units ;
1445+ gop .value_ptr .* .impl .associated_compilation_units = .unresolved ;
1446+
13531447 new_handle .uri = gop .key_ptr .* ;
13541448 gop .value_ptr .* .deinit ();
13551449 gop .value_ptr .*.* = new_handle ;
@@ -1724,30 +1818,53 @@ fn publishCimportDiagnostics(self: *DocumentStore, handle: *Handle) !void {
17241818 try self .diagnostics_collection .publishDiagnostics ();
17251819}
17261820
1821+ pub const UriFromImportStringResult = union (enum ) {
1822+ none ,
1823+ one : Uri ,
1824+ many : []const Uri ,
1825+
1826+ pub fn deinit (result : * UriFromImportStringResult , allocator : std.mem.Allocator ) void {
1827+ switch (result .* ) {
1828+ .none = > {},
1829+ .one = > | uri | uri .deinit (allocator ),
1830+ .many = > | uris | {
1831+ for (uris ) | uri | uri .deinit (allocator );
1832+ allocator .free (uris );
1833+ },
1834+ }
1835+ }
1836+ };
1837+
17271838/// takes the string inside a @import() node (without the quotation marks)
17281839/// and returns it's uri
17291840/// caller owns the returned memory
17301841/// **Thread safe** takes a shared lock
1731- pub fn uriFromImportStr (self : * DocumentStore , allocator : std.mem.Allocator , handle : * Handle , import_str : []const u8 ) error {OutOfMemory }! ? Uri {
1842+ pub fn uriFromImportStr (
1843+ self : * DocumentStore ,
1844+ allocator : std.mem.Allocator ,
1845+ handle : * Handle ,
1846+ import_str : []const u8 ,
1847+ ) error {OutOfMemory }! UriFromImportStringResult {
17321848 const tracy_zone = tracy .trace (@src ());
17331849 defer tracy_zone .end ();
17341850
17351851 if (std .mem .endsWith (u8 , import_str , ".zig" ) or std .mem .endsWith (u8 , import_str , ".zon" )) {
17361852 const base_path = handle .uri .toFsPath (allocator ) catch | err | switch (err ) {
17371853 error .OutOfMemory = > return error .OutOfMemory ,
1738- error .UnsupportedScheme = > return null ,
1854+ error .UnsupportedScheme = > return .none ,
17391855 };
17401856 defer allocator .free (base_path );
1741- return try resolveFileImportString (allocator , base_path , import_str );
1857+ const uri = try resolveFileImportString (allocator , base_path , import_str ) orelse return .none ;
1858+ return .{ .one = uri };
17421859 }
17431860
17441861 if (std .mem .eql (u8 , import_str , "std" )) {
1745- const zig_lib_dir = self .config .zig_lib_dir orelse return null ;
1862+ const zig_lib_dir = self .config .zig_lib_dir orelse return .none ;
17461863
17471864 const std_path = try zig_lib_dir .join (allocator , &.{ "std" , "std.zig" });
17481865 defer allocator .free (std_path );
17491866
1750- return try .fromPath (allocator , std_path );
1867+ return .{ . one = try .fromPath (allocator , std_path ) } ;
17511868 }
17521869
17531870 if (std .mem .eql (u8 , import_str , "builtin" )) {
@@ -1756,24 +1873,33 @@ pub fn uriFromImportStr(self: *DocumentStore, allocator: std.mem.Allocator, hand
17561873 .none , .unresolved = > {},
17571874 .resolved = > | resolved | {
17581875 if (resolved .build_file .builtin_uri ) | builtin_uri | {
1759- return try builtin_uri .dupe (allocator );
1876+ return .{ . one = try builtin_uri .dupe (allocator ) } ;
17601877 }
17611878 },
17621879 }
17631880 }
17641881 if (self .config .builtin_path ) | builtin_path | {
1765- return try .fromPath (allocator , builtin_path );
1882+ return .{ . one = try .fromPath (allocator , builtin_path ) } ;
17661883 }
1767- return null ;
1884+ return .none ;
17681885 }
17691886
1770- if (! supports_build_system ) return null ;
1887+ if (! supports_build_system ) return .none ;
17711888
17721889 if (std .mem .eql (u8 , import_str , "root" )) {
1773- switch (try handle .getAssociatedBuildFile (self )) {
1774- .none , .unresolved = > return null ,
1775- .resolved = > | result | return try .fromPath (allocator , result .root_source_file ),
1890+ const root_source_files = switch (try handle .getAssociatedCompilationUnits (self )) {
1891+ .none , .unresolved = > return .none ,
1892+ .resolved = > | root_source_files | root_source_files ,
1893+ };
1894+ var uris : std .ArrayList (Uri ) = try .initCapacity (allocator , root_source_files .len );
1895+ defer {
1896+ for (uris .items ) | uri | uri .deinit (allocator );
1897+ uris .deinit (allocator );
17761898 }
1899+ for (root_source_files ) | root_source_file | {
1900+ uris .appendAssumeCapacity (try .fromPath (allocator , root_source_file ));
1901+ }
1902+ return .{ .many = try uris .toOwnedSlice (allocator ) };
17771903 }
17781904
17791905 if (isBuildFile (handle .uri )) blk : {
@@ -1782,20 +1908,20 @@ pub fn uriFromImportStr(self: *DocumentStore, allocator: std.mem.Allocator, hand
17821908 defer build_file .unlockConfig ();
17831909
17841910 if (build_config .dependencies .map .get (import_str )) | path | {
1785- return try .fromPath (allocator , path );
1911+ return .{ . one = try .fromPath (allocator , path ) } ;
17861912 }
1787- return null ;
1913+ return .none ;
17881914 }
17891915
17901916 switch (try handle .getAssociatedBuildFile (self )) {
1791- .none , .unresolved = > return null ,
1917+ .none , .unresolved = > return .none ,
17921918 .resolved = > | resolved | {
1793- const build_config = resolved .build_file .tryLockConfig () orelse return null ;
1919+ const build_config = resolved .build_file .tryLockConfig () orelse return .none ;
17941920 defer resolved .build_file .unlockConfig ();
17951921
1796- const module = build_config .modules .map .get (resolved .root_source_file ) orelse return null ;
1797- const imported_root_source_file = module .import_table .map .get (import_str ) orelse return null ;
1798- return try .fromPath (allocator , imported_root_source_file );
1922+ const module = build_config .modules .map .get (resolved .root_source_file ) orelse return .none ;
1923+ const imported_root_source_file = module .import_table .map .get (import_str ) orelse return .none ;
1924+ return .{ . one = try .fromPath (allocator , imported_root_source_file ) } ;
17991925 },
18001926 }
18011927}
0 commit comments