feat(solc): improve error diagnostic (#2280)
This commit is contained in:
parent
d5831b2679
commit
279280c6fd
|
@ -46,7 +46,9 @@
|
||||||
//! [version pragma](https://docs.soliditylang.org/en/develop/layout-of-source-files.html#version-pragma),
|
//! [version pragma](https://docs.soliditylang.org/en/develop/layout-of-source-files.html#version-pragma),
|
||||||
//! which is defined on a per source file basis.
|
//! which is defined on a per source file basis.
|
||||||
|
|
||||||
use crate::{error::Result, utils, IncludePaths, ProjectPathsConfig, SolcError, Source, Sources};
|
use crate::{
|
||||||
|
error::Result, utils, IncludePaths, ProjectPathsConfig, SolcError, SolcVersion, Source, Sources,
|
||||||
|
};
|
||||||
use parse::{SolData, SolDataUnit, SolImport};
|
use parse::{SolData, SolDataUnit, SolImport};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use semver::VersionReq;
|
use semver::VersionReq;
|
||||||
|
@ -577,8 +579,7 @@ impl Graph {
|
||||||
// on first error, instead gather all the errors and return a bundled error message instead
|
// on first error, instead gather all the errors and return a bundled error message instead
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
// we also don't want duplicate error diagnostic
|
// we also don't want duplicate error diagnostic
|
||||||
let mut erroneous_nodes =
|
let mut erroneous_nodes = HashSet::with_capacity(self.edges.num_input_files);
|
||||||
std::collections::HashSet::with_capacity(self.edges.num_input_files);
|
|
||||||
|
|
||||||
// the sorted list of all versions
|
// the sorted list of all versions
|
||||||
let all_versions = if offline { Solc::installed_versions() } else { Solc::all_versions() };
|
let all_versions = if offline { Solc::installed_versions() } else { Solc::all_versions() };
|
||||||
|
@ -596,11 +597,20 @@ impl Graph {
|
||||||
self.retain_compatible_versions(idx, &mut candidates);
|
self.retain_compatible_versions(idx, &mut candidates);
|
||||||
|
|
||||||
if candidates.is_empty() && !erroneous_nodes.contains(&idx) {
|
if candidates.is_empty() && !erroneous_nodes.contains(&idx) {
|
||||||
|
// check if the version is even valid
|
||||||
|
if let Some(Err(version_err)) =
|
||||||
|
self.node(idx).check_available_version(&all_versions, offline)
|
||||||
|
{
|
||||||
|
let f = utils::source_name(&self.node(idx).path, &self.root).display();
|
||||||
|
errors.push(format!("Encountered invalid solc version in {f}: {version_err}"));
|
||||||
|
} else {
|
||||||
let mut msg = String::new();
|
let mut msg = String::new();
|
||||||
self.format_imports_list(idx, &mut msg).unwrap();
|
self.format_imports_list(idx, &mut msg).unwrap();
|
||||||
errors.push(format!(
|
errors.push(format!(
|
||||||
"Discovered incompatible solidity versions in following\n: {msg}"
|
"Discovered incompatible solidity versions in following\n: {msg}"
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
erroneous_nodes.insert(idx);
|
erroneous_nodes.insert(idx);
|
||||||
} else {
|
} else {
|
||||||
// found viable candidates, pick the most recent version that's already installed
|
// found viable candidates, pick the most recent version that's already installed
|
||||||
|
@ -888,6 +898,37 @@ impl Node {
|
||||||
pub fn unpack(&self) -> (&PathBuf, &Source) {
|
pub fn unpack(&self) -> (&PathBuf, &Source) {
|
||||||
(&self.path, &self.source)
|
(&self.path, &self.source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks that the file's version is even available.
|
||||||
|
///
|
||||||
|
/// This returns an error if the file's version is invalid semver, or is not available such as
|
||||||
|
/// 0.8.20, if the highest available version is `0.8.19`
|
||||||
|
fn check_available_version(
|
||||||
|
&self,
|
||||||
|
all_versions: &[SolcVersion],
|
||||||
|
offline: bool,
|
||||||
|
) -> Option<std::result::Result<(), SourceVersionError>> {
|
||||||
|
fn ensure_version(
|
||||||
|
v: &str,
|
||||||
|
all_versions: &[SolcVersion],
|
||||||
|
offline: bool,
|
||||||
|
) -> std::result::Result<(), SourceVersionError> {
|
||||||
|
let req: VersionReq =
|
||||||
|
v.parse().map_err(|err| SourceVersionError::InvalidVersion(v.to_string(), err))?;
|
||||||
|
|
||||||
|
if !all_versions.iter().any(|v| req.matches(v.as_ref())) {
|
||||||
|
return if offline {
|
||||||
|
Err(SourceVersionError::NoMatchingVersionOffline(req))
|
||||||
|
} else {
|
||||||
|
Err(SourceVersionError::NoMatchingVersion(req))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
let v = self.data.version.as_ref()?.data();
|
||||||
|
Some(ensure_version(v, all_versions, offline))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper type for formatting a node
|
/// Helper type for formatting a node
|
||||||
|
@ -907,6 +948,18 @@ impl<'a> fmt::Display for DisplayNode<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Errors thrown when checking the solc version of a file
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[allow(unused)]
|
||||||
|
enum SourceVersionError {
|
||||||
|
#[error("Failed to parse solidity version {0}: {1}")]
|
||||||
|
InvalidVersion(String, semver::Error),
|
||||||
|
#[error("No solc version exists that matches the version requirement: {0}")]
|
||||||
|
NoMatchingVersion(VersionReq),
|
||||||
|
#[error("No solc version installed that matches the version requirement: {0}")]
|
||||||
|
NoMatchingVersionOffline(VersionReq),
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -8,6 +8,7 @@ use ethers_solc::{
|
||||||
},
|
},
|
||||||
buildinfo::BuildInfo,
|
buildinfo::BuildInfo,
|
||||||
cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
|
cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
|
||||||
|
error::SolcError,
|
||||||
info::ContractInfo,
|
info::ContractInfo,
|
||||||
project_util::*,
|
project_util::*,
|
||||||
remappings::Remapping,
|
remappings::Remapping,
|
||||||
|
@ -1199,6 +1200,27 @@ library MyLib {
|
||||||
let bytecode = &contract.bytecode.as_ref().unwrap().object;
|
let bytecode = &contract.bytecode.as_ref().unwrap().object;
|
||||||
assert!(!bytecode.is_unlinked());
|
assert!(!bytecode.is_unlinked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_detect_invalid_version() {
|
||||||
|
let tmp = TempProject::dapptools().unwrap();
|
||||||
|
let content = r#"
|
||||||
|
pragma solidity ^0.100.10;
|
||||||
|
contract A {}
|
||||||
|
"#;
|
||||||
|
tmp.add_source("A", content).unwrap();
|
||||||
|
|
||||||
|
let out = tmp.compile().unwrap_err();
|
||||||
|
match out {
|
||||||
|
SolcError::Message(err) => {
|
||||||
|
assert_eq!(err, "Encountered invalid solc version in src/A.sol: No solc version exists that matches the version requirement: ^0.100.10");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_recompile_with_changes() {
|
fn can_recompile_with_changes() {
|
||||||
let mut tmp = TempProject::dapptools().unwrap();
|
let mut tmp = TempProject::dapptools().unwrap();
|
||||||
|
|
Loading…
Reference in New Issue