use crate::{resolver::Node, ProjectPathsConfig};
use alloy_json_abi::JsonAbi;
use core::fmt;
use foundry_compilers_artifacts::{
error::SourceLocation,
output_selection::OutputSelection,
remappings::Remapping,
sources::{Source, Sources},
BytecodeObject, CompactContractRef, Contract, FileToContractsMap, Severity, SourceFile,
};
use foundry_compilers_core::error::Result;
use rayon::prelude::*;
use semver::{Version, VersionReq};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
borrow::Cow,
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet},
fmt::{Debug, Display},
hash::Hash,
path::{Path, PathBuf},
sync::{Mutex, OnceLock},
};
pub mod multi;
pub mod solc;
pub mod vyper;
pub use vyper::*;
mod restrictions;
pub use restrictions::{CompilerSettingsRestrictions, RestrictionsWithVersion};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CompilerVersion {
Installed(Version),
Remote(Version),
}
impl CompilerVersion {
pub fn is_installed(&self) -> bool {
matches!(self, Self::Installed(_))
}
}
impl AsRef<Version> for CompilerVersion {
fn as_ref(&self) -> &Version {
match self {
Self::Installed(v) | Self::Remote(v) => v,
}
}
}
impl From<CompilerVersion> for Version {
fn from(s: CompilerVersion) -> Self {
match s {
CompilerVersion::Installed(v) | CompilerVersion::Remote(v) => v,
}
}
}
impl fmt::Display for CompilerVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_ref())
}
}
pub trait CompilerSettings:
Default + Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static
{
type Restrictions: CompilerSettingsRestrictions;
fn update_output_selection(&mut self, f: impl FnMut(&mut OutputSelection));
fn can_use_cached(&self, other: &Self) -> bool;
fn with_remappings(self, _remappings: &[Remapping]) -> Self {
self
}
fn with_base_path(self, _base_path: &Path) -> Self {
self
}
fn with_allow_paths(self, _allowed_paths: &BTreeSet<PathBuf>) -> Self {
self
}
fn with_include_paths(self, _include_paths: &BTreeSet<PathBuf>) -> Self {
self
}
fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool;
}
pub trait CompilerInput: Serialize + Send + Sync + Sized + Debug {
type Settings: CompilerSettings;
type Language: Language;
fn build(
sources: Sources,
settings: Self::Settings,
language: Self::Language,
version: Version,
) -> Self;
fn language(&self) -> Self::Language;
fn version(&self) -> &Version;
fn sources(&self) -> impl Iterator<Item = (&Path, &Source)>;
fn compiler_name(&self) -> Cow<'static, str>;
fn strip_prefix(&mut self, base: &Path);
}
pub trait SourceParser: Clone + Debug + Send + Sync {
type ParsedSource: ParsedSource;
fn new(config: &ProjectPathsConfig) -> Self;
fn read(&mut self, path: &Path) -> Result<Node<Self::ParsedSource>> {
Node::read(path)
}
fn parse_sources(
&mut self,
sources: &mut Sources,
) -> Result<Vec<(PathBuf, Node<Self::ParsedSource>)>> {
sources
.0
.par_iter()
.map(|(path, source)| {
let data = Self::ParsedSource::parse(source.as_ref(), path)?;
Ok((path.clone(), Node::new(path.clone(), source.clone(), data)))
})
.collect::<Result<_>>()
}
fn finalize_imports(
&mut self,
_nodes: &mut Vec<Node<Self::ParsedSource>>,
_include_paths: &BTreeSet<PathBuf>,
) -> Result<()> {
Ok(())
}
}
pub trait ParsedSource: Clone + Debug + Sized + Send {
type Language: Language;
fn parse(content: &str, file: &Path) -> Result<Self>;
fn version_req(&self) -> Option<&VersionReq>;
fn contract_names(&self) -> &[String];
fn language(&self) -> Self::Language;
fn resolve_imports<C>(
&self,
paths: &ProjectPathsConfig<C>,
include_paths: &mut BTreeSet<PathBuf>,
) -> Result<Vec<PathBuf>>;
fn compilation_dependencies<'a>(
&self,
_imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
) -> impl Iterator<Item = &'a Path>
where
Self: 'a,
{
vec![].into_iter()
}
}
pub trait CompilationError:
Serialize + Send + Sync + Display + Debug + Clone + PartialEq + Eq + 'static
{
fn is_warning(&self) -> bool;
fn is_error(&self) -> bool;
fn source_location(&self) -> Option<SourceLocation>;
fn severity(&self) -> Severity;
fn error_code(&self) -> Option<u64>;
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CompilerOutput<E, C> {
#[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")]
pub errors: Vec<E>,
#[serde(default = "BTreeMap::new")]
pub contracts: FileToContractsMap<C>,
#[serde(default)]
pub sources: BTreeMap<PathBuf, SourceFile>,
#[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
pub metadata: BTreeMap<String, serde_json::Value>,
}
impl<E, C> CompilerOutput<E, C> {
pub fn retain_files<F, I>(&mut self, files: I)
where
F: AsRef<Path>,
I: IntoIterator<Item = F>,
{
let files: HashSet<_> =
files.into_iter().map(|s| s.as_ref().to_string_lossy().to_lowercase()).collect();
self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
}
pub fn merge(&mut self, other: Self) {
self.errors.extend(other.errors);
self.contracts.extend(other.contracts);
self.sources.extend(other.sources);
}
pub fn join_all(&mut self, root: &Path) {
self.contracts = std::mem::take(&mut self.contracts)
.into_iter()
.map(|(path, contracts)| (root.join(path), contracts))
.collect();
self.sources = std::mem::take(&mut self.sources)
.into_iter()
.map(|(path, source)| (root.join(path), source))
.collect();
}
pub fn map_err<F, O: FnMut(E) -> F>(self, op: O) -> CompilerOutput<F, C> {
CompilerOutput {
errors: self.errors.into_iter().map(op).collect(),
contracts: self.contracts,
sources: self.sources,
metadata: self.metadata,
}
}
}
impl<E, C> Default for CompilerOutput<E, C> {
fn default() -> Self {
Self {
errors: Vec::new(),
contracts: BTreeMap::new(),
sources: BTreeMap::new(),
metadata: BTreeMap::new(),
}
}
}
pub trait Language:
Hash + Eq + Copy + Clone + Debug + Display + Send + Sync + Serialize + DeserializeOwned + 'static
{
const FILE_EXTENSIONS: &'static [&'static str];
}
pub trait CompilerContract: Serialize + Send + Sync + Debug + Clone + Eq + Sized {
fn abi_ref(&self) -> Option<&JsonAbi>;
fn bin_ref(&self) -> Option<&BytecodeObject>;
fn bin_runtime_ref(&self) -> Option<&BytecodeObject>;
fn as_compact_contract_ref(&self) -> CompactContractRef<'_> {
CompactContractRef {
abi: self.abi_ref(),
bin: self.bin_ref(),
bin_runtime: self.bin_runtime_ref(),
}
}
}
impl CompilerContract for Contract {
fn abi_ref(&self) -> Option<&JsonAbi> {
self.abi.as_ref()
}
fn bin_ref(&self) -> Option<&BytecodeObject> {
if let Some(ref evm) = self.evm {
evm.bytecode.as_ref().map(|c| &c.object)
} else {
None
}
}
fn bin_runtime_ref(&self) -> Option<&BytecodeObject> {
if let Some(ref evm) = self.evm {
evm.deployed_bytecode
.as_ref()
.and_then(|deployed| deployed.bytecode.as_ref().map(|evm| &evm.object))
} else {
None
}
}
}
#[auto_impl::auto_impl(&, Box, Arc)]
pub trait Compiler: Send + Sync + Clone {
type Input: CompilerInput<Settings = Self::Settings, Language = Self::Language>;
type CompilationError: CompilationError;
type CompilerContract: CompilerContract;
type Parser: SourceParser<ParsedSource: ParsedSource<Language = Self::Language>>;
type Settings: CompilerSettings;
type Language: Language;
fn compile(
&self,
input: &Self::Input,
) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>>;
fn available_versions(&self, language: &Self::Language) -> Vec<CompilerVersion>;
}
pub(crate) fn cache_version(
path: PathBuf,
args: &[String],
f: impl FnOnce(&Path) -> Result<Version>,
) -> Result<Version> {
#[allow(clippy::complexity)]
static VERSION_CACHE: OnceLock<Mutex<HashMap<(PathBuf, Vec<String>), Version>>> =
OnceLock::new();
let mut cache = VERSION_CACHE
.get_or_init(Default::default)
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match cache.entry((path, args.to_vec())) {
Entry::Occupied(entry) => Ok(entry.get().clone()),
Entry::Vacant(entry) => {
let path = &entry.key().0;
let _guard =
debug_span!("get_version", path = %path.file_name().map(|n| n.to_string_lossy()).unwrap_or_else(|| path.to_string_lossy()))
.entered();
let version = f(path)?;
entry.insert(version.clone());
Ok(version)
}
}
}