Skip to content

Commit fbc109a

Browse files
committed
feat: better error messages for incompatible versions
1 parent c16927b commit fbc109a

File tree

2 files changed

+70
-30
lines changed

2 files changed

+70
-30
lines changed

crates/compilers/src/resolver/mod.rs

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
547547
fn format_imports_list<W: std::fmt::Write>(
548548
&self,
549549
idx: usize,
550+
imports: impl IntoIterator<Item = usize>,
550551
f: &mut W,
551552
) -> std::result::Result<(), std::fmt::Error> {
552553
let node = self.node(idx);
@@ -555,7 +556,7 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
555556
write!(f, "{req}")?;
556557
}
557558
write!(f, " imports:")?;
558-
for dep in self.node_ids(idx).skip(1) {
559+
for dep in imports {
559560
let dep = self.node(dep);
560561
write!(f, "\n {} ", utils::source_name(&dep.path, &self.root).display())?;
561562
if let Some(req) = dep.data.version_req() {
@@ -566,18 +567,74 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
566567
Ok(())
567568
}
568569

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() {
570+
/// Filters incompatible versions from the `candidates`. It iterates over node imports and in
571+
/// case if there is no compatible version it returns the latest seen node id.
572+
fn retain_compatible_versions(
573+
&self,
574+
idx: usize,
575+
candidates: &mut Vec<&CompilerVersion>,
576+
offline: bool,
577+
) -> Result<(), String> {
578+
let mut all_versions = candidates.clone();
579+
580+
let nodes: Vec<_> = self.node_ids(idx).collect();
581+
let mut failed_node = None;
582+
for node in nodes.iter() {
583+
if let Some(req) = self.node(*node).data.version_req() {
574584
candidates.retain(|v| req.matches(v.as_ref()));
585+
586+
if candidates.is_empty() {
587+
failed_node = Some(*node);
588+
break;
589+
}
590+
}
591+
}
592+
593+
let Some(failed_node_idx) = failed_node else {
594+
// everything is fine
595+
return Ok(());
596+
};
597+
598+
// This now keeps data for the node which were the last one before we had no candidates
599+
// left. It means that there
600+
let failed_node = self.node(failed_node_idx);
601+
602+
if let Err(version_err) = failed_node.check_available_version(&all_versions, offline) {
603+
// check if the version is even valid
604+
let f = utils::source_name(&failed_node.path, &self.root).display();
605+
return Err(
606+
format!("Encountered invalid solc version in {f}: {version_err}").to_string()
607+
);
608+
} else {
609+
// if the node requirement makes sense, it means that there is at least one node
610+
// which requirement conflicts with it
611+
612+
// retain only versions compatible with the `failed_node`
613+
if let Some(req) = failed_node.data.version_req() {
614+
all_versions.retain(|v| req.matches(v.as_ref()));
575615
}
576-
if candidates.is_empty() {
577-
// nothing to filter anymore
578-
return;
616+
617+
// iterate over all the nodes once again and find the one incompatible
618+
for node in &nodes {
619+
if self.node(*node).check_available_version(&all_versions, offline).is_err() {
620+
let mut msg = String::new();
621+
622+
// avoid formatting root node as import
623+
let imports = if *node == idx {
624+
vec![failed_node_idx]
625+
} else {
626+
vec![*node, failed_node_idx]
627+
};
628+
629+
self.format_imports_list(idx, imports, &mut msg).unwrap();
630+
return Err(format!("Found incompatible versions:\n{msg}"));
631+
}
579632
}
580633
}
634+
635+
let mut msg = String::new();
636+
self.format_imports_list(idx, nodes[1..].to_vec(), &mut msg).unwrap();
637+
Err(format!("Found incompatible versions:\n{msg}"));
581638
}
582639

583640
fn input_nodes_by_language(&self) -> HashMap<D::Language, Vec<usize>> {
@@ -619,8 +676,6 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
619676
// exit on first error, instead gather all the errors and return a bundled
620677
// error message instead
621678
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);
624679

625680
// the sorted list of all versions
626681
let all_versions = if offline {
@@ -649,23 +704,8 @@ impl<L: Language, D: ParsedSource<Language = L>> Graph<D> {
649704
let mut candidates = all_versions.iter().collect::<Vec<_>>();
650705
// remove all incompatible versions from the candidates list by checking the node
651706
// 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);
707+
if let Err(err) = self.retain_compatible_versions(idx, &mut candidates, offline) {
708+
errors.push(err);
669709
} else {
670710
// found viable candidates, pick the most recent version that's already
671711
// installed
@@ -878,7 +918,7 @@ impl<D: ParsedSource> Node<D> {
878918
/// 0.8.20, if the highest available version is `0.8.19`
879919
fn check_available_version(
880920
&self,
881-
all_versions: &[CompilerVersion],
921+
all_versions: &[&CompilerVersion],
882922
offline: bool,
883923
) -> std::result::Result<(), SourceVersionError> {
884924
let Some(req) = self.data.version_req() else { return Ok(()) };

crates/core/src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{
55
};
66
use thiserror::Error;
77

8-
pub type Result<T> = std::result::Result<T, SolcError>;
8+
pub type Result<T, E = SolcError> = std::result::Result<T, E>;
99

1010
#[allow(unused_macros)]
1111
#[macro_export]

0 commit comments

Comments
 (0)