@@ -64,6 +64,7 @@ use std::{
6464 io,
6565 path:: { Path , PathBuf } ,
6666} ;
67+ use yansi:: { Color , Paint } ;
6768
6869pub mod parse;
6970mod tree;
@@ -547,37 +548,92 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
547548 fn format_imports_list < W : std:: fmt:: Write > (
548549 & self ,
549550 idx : usize ,
551+ incompatible : HashSet < usize > ,
550552 f : & mut W ,
551553 ) -> std:: result:: Result < ( ) , std:: fmt:: Error > {
552- let node = self . node ( idx) ;
553- write ! ( f, "{} " , utils:: source_name( & node. path, & self . root) . display( ) ) ?;
554- if let Some ( req) = node. data . version_req ( ) {
555- write ! ( f, "{req}" ) ?;
556- }
554+ let format_node = |idx, f : & mut W | {
555+ let node = self . node ( idx) ;
556+ let color = if incompatible. contains ( & idx) { Color :: Red } else { Color :: White } ;
557+
558+ let mut line = utils:: source_name ( & node. path , & self . root ) . display ( ) . to_string ( ) ;
559+ if let Some ( req) = node. data . version_req ( ) {
560+ line. push_str ( & format ! ( " {req}" ) ) ;
561+ }
562+
563+ write ! ( f, "{}" , line. paint( color) )
564+ } ;
565+ format_node ( idx, f) ?;
557566 write ! ( f, " imports:" ) ?;
558567 for dep in self . node_ids ( idx) . skip ( 1 ) {
559- let dep = self . node ( dep) ;
560- write ! ( f, "\n {} " , utils:: source_name( & dep. path, & self . root) . display( ) ) ?;
561- if let Some ( req) = dep. data . version_req ( ) {
562- write ! ( f, "{req}" ) ?;
563- }
568+ write ! ( f, "\n " ) ?;
569+ format_node ( dep, f) ?;
564570 }
565571
566572 Ok ( ( ) )
567573 }
568574
569- /// Filters incompatible versions from the `candidates`.
570- fn retain_compatible_versions ( & self , idx : usize , candidates : & mut Vec < & CompilerVersion > ) {
571- let nodes: HashSet < _ > = self . node_ids ( idx) . collect ( ) ;
572- for node in nodes {
573- if let Some ( req) = & self . node ( node) . data . version_req ( ) {
575+ /// Filters incompatible versions from the `candidates`. It iterates over node imports and in
576+ /// case if there is no compatible version it returns the latest seen node id.
577+ fn retain_compatible_versions (
578+ & self ,
579+ idx : usize ,
580+ candidates : & mut Vec < & CompilerVersion > ,
581+ offline : bool ,
582+ ) -> Result < ( ) , String > {
583+ let mut all_versions = candidates. clone ( ) ;
584+
585+ let nodes: Vec < _ > = self . node_ids ( idx) . collect ( ) ;
586+ let mut failed_node = None ;
587+ for node in nodes. iter ( ) {
588+ if let Some ( req) = self . node ( * node) . data . version_req ( ) {
574589 candidates. retain ( |v| req. matches ( v. as_ref ( ) ) ) ;
590+
591+ if candidates. is_empty ( ) {
592+ failed_node = Some ( * node) ;
593+ break ;
594+ }
595+ }
596+ }
597+
598+ let Some ( failed_node_idx) = failed_node else {
599+ // everything is fine
600+ return Ok ( ( ) ) ;
601+ } ;
602+
603+ // This now keeps data for the node which were the last one before we had no candidates
604+ // left. It means that there
605+ let failed_node = self . node ( failed_node_idx) ;
606+
607+ if let Err ( version_err) = failed_node. check_available_version ( & all_versions, offline) {
608+ // check if the version is even valid
609+ let f = utils:: source_name ( & failed_node. path , & self . root ) . display ( ) ;
610+ return Err (
611+ format ! ( "Encountered invalid solc version in {f}: {version_err}" ) . to_string ( )
612+ ) ;
613+ } else {
614+ // if the node requirement makes sense, it means that there is at least one node
615+ // which requirement conflicts with it
616+
617+ // retain only versions compatible with the `failed_node`
618+ if let Some ( req) = failed_node. data . version_req ( ) {
619+ all_versions. retain ( |v| req. matches ( v. as_ref ( ) ) ) ;
575620 }
576- if candidates. is_empty ( ) {
577- // nothing to filter anymore
578- return ;
621+
622+ // iterate over all the nodes once again and find the one incompatible
623+ for node in & nodes {
624+ if self . node ( * node) . check_available_version ( & all_versions, offline) . is_err ( ) {
625+ let mut msg = "Found incompatible versions:\n " . white ( ) . to_string ( ) ;
626+
627+ self . format_imports_list ( idx, [ * node, failed_node_idx] . into ( ) , & mut msg)
628+ . unwrap ( ) ;
629+ return Err ( msg) ;
630+ }
579631 }
580632 }
633+
634+ let mut msg = "Found incompatible versions:\n " . white ( ) . to_string ( ) ;
635+ self . format_imports_list ( idx, nodes. into_iter ( ) . collect ( ) , & mut msg) . unwrap ( ) ;
636+ Err ( msg)
581637 }
582638
583639 fn input_nodes_by_language ( & self ) -> HashMap < D :: Language , Vec < usize > > {
@@ -619,8 +675,6 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
619675 // exit on first error, instead gather all the errors and return a bundled
620676 // error message instead
621677 let mut errors = Vec :: new ( ) ;
622- // we also don't want duplicate error diagnostic
623- let mut erroneous_nodes = HashSet :: with_capacity ( self . edges . num_input_files ) ;
624678
625679 // the sorted list of all versions
626680 let all_versions = if offline {
@@ -649,23 +703,8 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
649703 let mut candidates = all_versions. iter ( ) . collect :: < Vec < _ > > ( ) ;
650704 // remove all incompatible versions from the candidates list by checking the node
651705 // and all its imports
652- self . retain_compatible_versions ( idx, & mut candidates) ;
653-
654- if candidates. is_empty ( ) && !erroneous_nodes. contains ( & idx) {
655- // check if the version is even valid
656- let node = self . node ( idx) ;
657- if let Err ( version_err) = node. check_available_version ( & all_versions, offline) {
658- let f = utils:: source_name ( & node. path , & self . root ) . display ( ) ;
659- errors. push ( format ! (
660- "Encountered invalid solc version in {f}: {version_err}"
661- ) ) ;
662- } else {
663- let mut msg = String :: new ( ) ;
664- self . format_imports_list ( idx, & mut msg) . unwrap ( ) ;
665- errors. push ( format ! ( "Found incompatible versions:\n {msg}" ) ) ;
666- }
667-
668- erroneous_nodes. insert ( idx) ;
706+ if let Err ( err) = self . retain_compatible_versions ( idx, & mut candidates, offline) {
707+ errors. push ( err) ;
669708 } else {
670709 // found viable candidates, pick the most recent version that's already
671710 // installed
@@ -878,7 +917,7 @@ impl<D: ParsedSource> Node<D> {
878917 /// 0.8.20, if the highest available version is `0.8.19`
879918 fn check_available_version (
880919 & self ,
881- all_versions : & [ CompilerVersion ] ,
920+ all_versions : & [ & CompilerVersion ] ,
882921 offline : bool ,
883922 ) -> std:: result:: Result < ( ) , SourceVersionError > {
884923 let Some ( req) = self . data . version_req ( ) else { return Ok ( ( ) ) } ;
@@ -1013,6 +1052,32 @@ src/Dapp.t.sol >=0.6.6
10131052 ) ;
10141053 }
10151054
1055+ #[ test]
1056+ #[ cfg( feature = "svm-solc" ) ]
1057+ fn test_print_unresolved ( ) {
1058+ let root =
1059+ Path :: new ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "../../test-data/incompatible-pragmas" ) ;
1060+ let paths = ProjectPathsConfig :: dapptools ( & root) . unwrap ( ) ;
1061+ let graph = Graph :: < SolData > :: resolve ( & paths) . unwrap ( ) ;
1062+ let Err ( SolcError :: Message ( err) ) = graph. get_input_node_versions (
1063+ false ,
1064+ & Default :: default ( ) ,
1065+ & crate :: solc:: SolcCompiler :: AutoDetect ,
1066+ ) else {
1067+ panic ! ( "expected error" ) ;
1068+ } ;
1069+
1070+ snapbox:: assert_data_eq!(
1071+ err,
1072+ snapbox:: str ![ [ r#"
1073+ [37mFound incompatible versions:
1074+ [0m[31msrc/A.sol =0.8.25[0m imports:
1075+ [37msrc/B.sol[0m
1076+ [31msrc/C.sol =0.7.0[0m
1077+ "# ] ]
1078+ ) ;
1079+ }
1080+
10161081 #[ cfg( target_os = "linux" ) ]
10171082 #[ test]
10181083 fn can_read_different_case ( ) {
0 commit comments