@@ -7,7 +7,7 @@ use crate::error::{Error, ErrorCollector, RichError, Span};
77use crate :: parse:: { self , AliasedIdentifier , ParseFromStrWithErrors , Visibility } ;
88use crate :: str:: { AliasName , FunctionName , Identifier } ;
99use crate :: types:: AliasedType ;
10- use crate :: { get_full_path , impl_eq_hash, LibTable , SourceFile , SourceName } ;
10+ use crate :: { impl_eq_hash, DependencyMap , SourceFile , SourceName } ;
1111
1212/// Graph Node: One file = One module
1313#[ derive( Debug , Clone ) ]
@@ -25,7 +25,7 @@ pub struct ProjectGraph {
2525
2626 /// Fast lookup: Path -> ID
2727 /// Solves the duplicate problem (so as not to parse a.simf twice)
28- pub libraries : Arc < LibTable > ,
28+ pub dependency_map : Arc < DependencyMap > ,
2929 pub lookup : HashMap < SourceName , usize > ,
3030 pub paths : Arc < [ SourceName ] > ,
3131
@@ -381,7 +381,7 @@ impl ProjectGraph {
381381
382382 pub fn new (
383383 root_source : SourceFile ,
384- libraries : Arc < LibTable > ,
384+ dependency_map : Arc < DependencyMap > ,
385385 root_program : & parse:: Program ,
386386 handler : & mut ErrorCollector ,
387387 ) -> Option < Self > {
@@ -416,11 +416,10 @@ impl ProjectGraph {
416416 // PHASE 1: Resolve Imports
417417 for elem in current_program. items ( ) {
418418 if let parse:: Item :: Use ( use_decl) = elem {
419- match get_full_path ( & libraries, use_decl) {
419+ match resolve_single_import ( importer_source. clone ( ) , use_decl, & dependency_map)
420+ {
420421 Ok ( path) => valid_imports. push ( ( path, * use_decl. span ( ) ) ) ,
421- Err ( err) => {
422- resolution_errors. push ( err. with_source ( importer_source. clone ( ) ) )
423- }
422+ Err ( err) => resolution_errors. push ( err) ,
424423 }
425424 }
426425 }
@@ -464,7 +463,7 @@ impl ProjectGraph {
464463 } else {
465464 Some ( Self {
466465 modules,
467- libraries ,
466+ dependency_map ,
468467 lookup,
469468 paths : paths. into ( ) ,
470469 dependencies,
@@ -673,13 +672,27 @@ impl ProjectGraph {
673672 let mut errors: Vec < RichError > = Vec :: new ( ) ;
674673 match elem {
675674 parse:: Item :: Use ( use_decl) => {
675+ let full_path = match resolve_single_import (
676+ importer_source. clone ( ) ,
677+ use_decl,
678+ & self . dependency_map ,
679+ ) {
680+ Ok ( path) => path,
681+ Err ( err) => {
682+ handler. push ( err) ;
683+ continue ;
684+ }
685+ } ;
686+
687+ /*
676688 let full_path = match get_full_path(&self.libraries, use_decl) {
677689 Ok(path) => path,
678690 Err(err) => {
679691 handler.push(err.with_source(importer_source.clone()));
680692 continue;
681693 }
682694 };
695+ */
683696 let source_full_path = SourceName :: Real ( Arc :: from ( full_path) ) ;
684697 let ind = self . lookup [ & source_full_path] ;
685698
@@ -758,6 +771,33 @@ impl ProjectGraph {
758771 }
759772}
760773
774+ /// Resolves a single `use` declaration into a physical file path.
775+ fn resolve_single_import (
776+ importer_source : SourceFile ,
777+ use_decl : & parse:: UseDecl ,
778+ dependency_map : & DependencyMap ,
779+ ) -> Result < PathBuf , RichError > {
780+ // TODO: @LesterEvSe or someone else, reconsider this architectural approach.
781+ // Consider removing this `match` statement, or dropping `SourceName` from `paths` and `lookup`.
782+ let curr_path = match importer_source. name ( ) {
783+ SourceName :: Real ( path) => path,
784+ SourceName :: Virtual ( name) => {
785+ // Notice we use `return Err(...)` here instead of `continue`
786+ return Err ( RichError :: new (
787+ Error :: Resolution ( format ! (
788+ "Virtual source '{name}' cannot be used to resolve library imports"
789+ ) ) ,
790+ * use_decl. span ( ) ,
791+ ) ) ;
792+ }
793+ } ;
794+
795+ match dependency_map. resolve_path ( & curr_path, use_decl) {
796+ Ok ( path) => Ok ( path) ,
797+ Err ( err) => Err ( err. with_source ( importer_source. clone ( ) ) ) ,
798+ }
799+ }
800+
761801/// C3 Merge Algorithm
762802///
763803/// Merges a list of sequences (parent linearizations) into a single sequence.
@@ -970,9 +1010,30 @@ pub(crate) mod tests {
9701010 ( program. expect ( "Root parsing failed internally" ) , source)
9711011 }
9721012
973- /// Sets up a graph with "lib" mapped to "libs/lib".
974- /// Files format: vec![("main.simf", "content"), ("libs/lib/A.simf", "content")]
975- fn setup_graph ( files : Vec < ( & str , & str ) > ) -> ( ProjectGraph , HashMap < String , usize > , TempDir ) {
1013+ /// Bootstraps a mock file system and attempts to construct a `ProjectGraph`.
1014+ ///
1015+ /// This is the low-level, non-panicking test helper. It is designed specifically for
1016+ /// "negative tests" where you expect the graph construction to fail (e.g., due to syntax
1017+ /// errors in an imported dependency).
1018+ ///
1019+ /// The mock environment automatically maps the alias `"lib"` to the `"libs/lib"` directory.
1020+ ///
1021+ /// # Arguments
1022+ /// * `files` - A vector of tuples containing `(file_path, file_content)`.
1023+ /// **Note:** One of the files *must* be named exactly `"main.simf"`.
1024+ ///
1025+ /// # Returns
1026+ /// A tuple containing:
1027+ /// 1. `Option<ProjectGraph>` - `Some` if construction succeeded, `None` if compilation failed.
1028+ /// 2. `ErrorCollector` - Contains all diagnostics emitted during parsing and resolution.
1029+ /// 3. `TempDir` - The temporary directory. It must be kept alive until the test completes.
1030+ ///
1031+ /// # Panics
1032+ /// Panics if the `files` vector does not contain a `"main.simf"` entry, or if writing
1033+ /// the mock files to the OS filesystem fails.
1034+ fn setup_graph_raw (
1035+ files : Vec < ( & str , & str ) > ,
1036+ ) -> ( Option < ProjectGraph > , ErrorCollector , TempDir ) {
9761037 let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
9771038
9781039 // 1. Create Files
@@ -986,20 +1047,55 @@ pub(crate) mod tests {
9861047 let root_p = root_path. expect ( "Tests must define 'main.simf'" ) ;
9871048
9881049 // 2. Setup Libraries (Hardcoded "lib" -> "libs/lib" for simplicity in tests)
989- let mut lib_map = HashMap :: new ( ) ;
990- lib_map. insert ( "lib" . to_string ( ) , temp_dir. path ( ) . join ( "libs/lib" ) ) ;
1050+ let mut dependency_map = DependencyMap :: new ( ) ;
1051+ dependency_map. test_insert_without_canonicalize (
1052+ temp_dir. path ( ) , // The root of mock project
1053+ "lib" . to_string ( ) ,
1054+ & temp_dir. path ( ) . join ( "libs/lib" ) ,
1055+ ) ;
9911056
9921057 // 3. Parse & Build
9931058 let ( root_program, source) = parse_root ( & root_p) ;
994-
9951059 let mut handler = ErrorCollector :: new ( ) ;
9961060
997- let graph = ProjectGraph :: new ( source, Arc :: from ( lib_map) , & root_program, & mut handler)
998- . expect (
999- "setup_graph expects a valid graph construction. Use manual setup for error tests." ,
1000- ) ;
1061+ let result = ProjectGraph :: new (
1062+ source,
1063+ Arc :: from ( dependency_map) ,
1064+ & root_program,
1065+ & mut handler,
1066+ ) ;
1067+
1068+ // Return the raw result and the handler so the test can inspect the errors
1069+ ( result, handler, temp_dir)
1070+ }
10011071
1002- // 4. Create Lookup (File Name -> ID) for easier asserting
1072+ /// Bootstraps a mock file system and constructs a valid `ProjectGraph`.
1073+ ///
1074+ /// This is the standard test helper for "happy path" scenarios. It wraps [`setup_graph_raw`]
1075+ /// and mathematically guarantees that the graph construction succeeds. It also generates a
1076+ /// convenient filename-to-ID lookup map to make asserting on specific files easier.
1077+ ///
1078+ /// # Arguments
1079+ /// * `files` - A vector of tuples containing `(file_path, file_content)`.
1080+ /// **Note:** One of the files *must* be named exactly `"main.simf"`.
1081+ ///
1082+ /// # Returns
1083+ /// A tuple containing:
1084+ /// 1. `ProjectGraph` - The fully constructed, valid dependency graph.
1085+ /// 2. `HashMap<String, usize>` - A mapping of simple filenames (e.g., `"math.simf"`) to their node IDs.
1086+ /// 3. `TempDir` - The temporary directory. It must be kept alive until the test completes.
1087+ ///
1088+ /// # Panics
1089+ /// Panics if the compiler encounters any errors during parsing or resolution,
1090+ /// or if `"main.simf"` is missing. For testing compiler errors, use [`setup_graph_raw`] instead.
1091+ fn setup_graph ( files : Vec < ( & str , & str ) > ) -> ( ProjectGraph , HashMap < String , usize > , TempDir ) {
1092+ let ( graph_result, _handler, temp_dir) = setup_graph_raw ( files) ;
1093+
1094+ let graph = graph_result. expect (
1095+ "setup_graph expects a valid graph construction. Use manual setup for error tests." ,
1096+ ) ;
1097+
1098+ // Create Lookup (File Name -> ID) for easier asserting
10031099 let mut file_ids = HashMap :: new ( ) ;
10041100 for ( source_name, id) in & graph. lookup {
10051101 let simple_name = match source_name {
@@ -1300,37 +1396,7 @@ pub(crate) mod tests {
13001396 assert ! ( graph. dependencies[ & ids[ "main" ] ] . is_empty( ) ) ;
13011397 }
13021398
1303- #[ test]
1304- fn test_missing_file_error ( ) {
1305- // MANUAL SETUP REQUIRED
1306- // We cannot use `setup_graph` here because we expect `ProjectGraph::new` to fail/return None.
1307-
1308- let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
1309- let root_path = create_simf_file ( temp_dir. path ( ) , "main.simf" , "use lib::ghost::Phantom;" ) ;
1310- // We purposefully DO NOT create ghost.simf
1311-
1312- let mut lib_map = HashMap :: new ( ) ;
1313- lib_map. insert ( "lib" . to_string ( ) , temp_dir. path ( ) . join ( "libs/lib" ) ) ;
1314-
1315- let ( root_program, root_source) = parse_root ( & root_path) ;
1316- let mut handler = ErrorCollector :: new ( ) ;
1317-
1318- let result =
1319- ProjectGraph :: new ( root_source, Arc :: from ( lib_map) , & root_program, & mut handler) ;
1320-
1321- assert ! ( result. is_none( ) , "Graph construction should fail" ) ;
1322- assert ! ( !handler. get( ) . is_empty( ) ) ;
1323-
1324- let error_msg = handler. to_string ( ) ;
1325- assert ! (
1326- error_msg. contains( "File not found" ) || error_msg. contains( "ghost.simf" ) ,
1327- "Error message should mention 'ghost.simf' or 'File not found'. Got: {}" ,
1328- error_msg
1329- ) ;
1330- }
1331-
13321399 // Tests for aliases
1333- // TODO: @LesterEvSe, @Sdoba16 add more tests for alias
13341400 #[ test]
13351401 fn test_renaming_with_use ( ) {
13361402 // Scenario: Renaming imports.
@@ -1555,27 +1621,29 @@ Try reordering your `use` statements to avoid cross-wiring."#
15551621 }
15561622
15571623 // --- Dependent File Error Display Tests ---
1558-
15591624 #[ test]
1560- #[ ignore = "TODO(Error_Formatting): The compiler currently strips the .simf extension from file paths during graph construction. This test expects the extension to be preserved." ]
1561- fn test_display_error_in_imported_dependency ( ) {
1562- let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
1563- let root_path = create_simf_file ( temp_dir. path ( ) , "main.simf" , "use lib::math::add;" ) ;
1564-
1565- create_simf_file (
1566- temp_dir. path ( ) ,
1567- "libs/lib/math.simf" ,
1568- "pub fn add(a: u32 b: u32) {}" ,
1569- ) ;
1625+ fn test_missing_file_error ( ) {
1626+ let ( result, handler, _dir) =
1627+ setup_graph_raw ( vec ! [ ( "main.simf" , "use lib::ghost::Phantom;" ) ] ) ;
15701628
1571- let mut lib_map = HashMap :: new ( ) ;
1572- lib_map . insert ( "lib" . to_string ( ) , temp_dir . path ( ) . join ( "libs/lib" ) ) ;
1629+ assert ! ( result . is_none ( ) , "Graph construction should fail" ) ;
1630+ assert ! ( handler . has_errors ( ) ) ;
15731631
1574- let ( root_program, root_source) = parse_root ( & root_path) ;
1575- let mut handler = ErrorCollector :: new ( ) ;
1632+ let error_msg = handler. to_string ( ) ;
1633+ assert ! (
1634+ error_msg. contains( "File not found" ) || error_msg. contains( "ghost.simf" ) ,
1635+ "Error message should mention 'ghost.simf' or 'File not found'. Got: {}" ,
1636+ error_msg
1637+ ) ;
1638+ }
15761639
1577- let result =
1578- ProjectGraph :: new ( root_source, Arc :: from ( lib_map) , & root_program, & mut handler) ;
1640+ #[ test]
1641+ #[ ignore = "TODO(Error_Formatting): The compiler currently strips the .simf extension from file paths during graph construction. This test expects the extension to be preserved." ]
1642+ fn test_display_error_in_imported_dependency ( ) {
1643+ let ( result, handler, _dir) = setup_graph_raw ( vec ! [
1644+ ( "main.simf" , "use lib::math::add;" ) ,
1645+ ( "libs/lib/math.simf" , "pub fn add(a: u32 b: u32) {}" ) , // NOTE: The comma is missing on purpose.
1646+ ] ) ;
15791647
15801648 assert ! (
15811649 result. is_none( ) ,
0 commit comments