Skip to content

Commit

Permalink
feat: replace solang with solar (#215)
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes authored Nov 11, 2024
1 parent 21617d0 commit 034ecd6
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 290 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ semver = { version = "1.0", features = ["serde"] }
serde = { version = "1", features = ["derive", "rc"] }
serde_json = "1.0"
similar-asserts = "1"
solang-parser = { version = "=0.3.3", default-features = false }
solar-parse = { version = "=0.1.0", default-features = false }
svm = { package = "svm-rs", version = "0.5", default-features = false }
tempfile = "3.9"
thiserror = "1"
Expand Down
2 changes: 1 addition & 1 deletion crates/compilers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ md-5.workspace = true
thiserror.workspace = true
path-slash.workspace = true
yansi.workspace = true
solang-parser.workspace = true
solar-parse.workspace = true
once_cell = { workspace = true, optional = true }
futures-util = { workspace = true, optional = true }
tokio = { workspace = true, optional = true }
Expand Down
10 changes: 9 additions & 1 deletion crates/compilers/src/compilers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,25 @@ pub trait CompilerInput: Serialize + Send + Sync + Sized + Debug {
pub trait ParsedSource: Debug + Sized + Send + Clone {
type Language: Language;

/// Parses the content of the source file.
fn parse(content: &str, file: &Path) -> Result<Self>;

/// Returns the version requirement of the source.
fn version_req(&self) -> Option<&VersionReq>;

/// Returns a list of contract names defined in the source.
fn contract_names(&self) -> &[String];

/// Returns the language of the source.
fn language(&self) -> Self::Language;

/// Invoked during import resolution. Should resolve imports for the given source, and populate
/// include_paths for compilers which support this config.
fn resolve_imports<C>(
&self,
paths: &ProjectPathsConfig<C>,
include_paths: &mut BTreeSet<PathBuf>,
) -> Result<Vec<PathBuf>>;
fn language(&self) -> Self::Language;

/// Used to configure [OutputSelection] for sparse builds. In certain cases, we might want to
/// include some of the file dependencies into the compiler output even if we might not be
Expand Down
21 changes: 14 additions & 7 deletions crates/compilers/src/compilers/multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,14 +316,10 @@ impl ParsedSource for MultiCompilerParsedSource {
}
}

fn resolve_imports<C>(
&self,
paths: &crate::ProjectPathsConfig<C>,
include_paths: &mut BTreeSet<PathBuf>,
) -> Result<Vec<PathBuf>> {
fn contract_names(&self) -> &[String] {
match self {
Self::Solc(parsed) => parsed.resolve_imports(paths, include_paths),
Self::Vyper(parsed) => parsed.resolve_imports(paths, include_paths),
Self::Solc(parsed) => parsed.contract_names(),
Self::Vyper(parsed) => parsed.contract_names(),
}
}

Expand All @@ -334,6 +330,17 @@ impl ParsedSource for MultiCompilerParsedSource {
}
}

fn resolve_imports<C>(
&self,
paths: &crate::ProjectPathsConfig<C>,
include_paths: &mut BTreeSet<PathBuf>,
) -> Result<Vec<PathBuf>> {
match self {
Self::Solc(parsed) => parsed.resolve_imports(paths, include_paths),
Self::Vyper(parsed) => parsed.resolve_imports(paths, include_paths),
}
}

fn compilation_dependencies<'a>(
&self,
imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
Expand Down
17 changes: 10 additions & 7 deletions crates/compilers/src/compilers/solc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use foundry_compilers_artifacts::{
Error, Settings, Severity, SolcInput,
};
use foundry_compilers_core::error::Result;
use itertools::Itertools;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::{
Expand Down Expand Up @@ -260,12 +259,8 @@ impl ParsedSource for SolData {
self.version_req.as_ref()
}

fn resolve_imports<C>(
&self,
_paths: &crate::ProjectPathsConfig<C>,
_include_paths: &mut BTreeSet<PathBuf>,
) -> Result<Vec<PathBuf>> {
Ok(self.imports.iter().map(|i| i.data().path().to_path_buf()).collect_vec())
fn contract_names(&self) -> &[String] {
&self.contract_names
}

fn language(&self) -> Self::Language {
Expand All @@ -276,6 +271,14 @@ impl ParsedSource for SolData {
}
}

fn resolve_imports<C>(
&self,
_paths: &crate::ProjectPathsConfig<C>,
_include_paths: &mut BTreeSet<PathBuf>,
) -> Result<Vec<PathBuf>> {
Ok(self.imports.iter().map(|i| i.data().path().to_path_buf()).collect())
}

fn compilation_dependencies<'a>(
&self,
imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
Expand Down
12 changes: 8 additions & 4 deletions crates/compilers/src/compilers/vyper/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ impl ParsedSource for VyperParsedSource {
self.version_req.as_ref()
}

fn contract_names(&self) -> &[String] {
&[]
}

fn language(&self) -> Self::Language {
VyperLanguage
}

fn resolve_imports<C>(
&self,
paths: &ProjectPathsConfig<C>,
Expand Down Expand Up @@ -137,10 +145,6 @@ impl ParsedSource for VyperParsedSource {
}
Ok(imports)
}

fn language(&self) -> Self::Language {
VyperLanguage
}
}

/// Parses given source trying to find all import directives.
Expand Down
19 changes: 10 additions & 9 deletions crates/compilers/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,36 +131,37 @@ impl ProjectPathsConfig<SolcLanguage> {
let mut result = String::new();

for path in ordered_deps.iter() {
let node_id = graph.files().get(path).ok_or_else(|| {
let node_id = *graph.files().get(path).ok_or_else(|| {
SolcError::msg(format!("cannot resolve file at {}", path.display()))
})?;
let node = graph.node(*node_id);
let node = graph.node(node_id);
node.data.parse_result()?;
let content = node.content();

// Firstly we strip all licesnses, verson pragmas
// We keep target file pragma and license placing them in the beginning of the result.
let mut ranges_to_remove = Vec::new();

if let Some(license) = &node.data.license {
ranges_to_remove.push(license.loc());
ranges_to_remove.push(license.span());
if *path == flatten_target {
result.push_str(&content[license.loc()]);
result.push_str(&content[license.span()]);
result.push('\n');
}
}
if let Some(version) = &node.data.version {
let content = &content[version.loc()];
ranges_to_remove.push(version.loc());
let content = &content[version.span()];
ranges_to_remove.push(version.span());
version_pragmas.push(content);
}
if let Some(experimental) = &node.data.experimental {
ranges_to_remove.push(experimental.loc());
ranges_to_remove.push(experimental.span());
if experimental_pragma.is_none() {
experimental_pragma = Some(content[experimental.loc()].to_owned());
experimental_pragma = Some(content[experimental.span()].to_owned());
}
}
for import in &node.data.imports {
ranges_to_remove.push(import.loc());
ranges_to_remove.push(import.span());
}
ranges_to_remove.sort_by_key(|loc| loc.start);

Expand Down
57 changes: 8 additions & 49 deletions crates/compilers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,16 @@ use compile::output::contracts::VersionedContracts;
use compilers::multi::MultiCompiler;
use derivative::Derivative;
use foundry_compilers_artifacts::solc::{
output_selection::OutputSelection,
sources::{Source, SourceCompilationKind, Sources},
Contract, Severity, SourceFile, StandardJsonCompilerInput,
};
use foundry_compilers_core::error::{Result, SolcError, SolcIoError};
use output::sources::{VersionedSourceFile, VersionedSourceFiles};
use project::ProjectCompiler;
use semver::Version;
use solang_parser::pt::SourceUnitPart;
use solc::SolcSettings;
use std::{
collections::{BTreeMap, HashMap, HashSet},
fs,
path::{Path, PathBuf},
};

Expand Down Expand Up @@ -361,62 +358,24 @@ impl<T: ArtifactOutput, C: Compiler> Project<C, T> {
Ok(())
}

/// Runs solc compiler without requesting any output and collects a mapping from contract names
/// to source files containing artifact with given name.
fn collect_contract_names_solc(&self) -> Result<HashMap<String, Vec<PathBuf>>>
where
T: Clone,
C: Clone,
{
let mut temp_project = (*self).clone();
temp_project.no_artifacts = true;
temp_project.settings.update_output_selection(|selection| {
*selection = OutputSelection::common_output_selection(["abi".to_string()]);
});

let output = temp_project.compile()?;

if output.has_compiler_errors() {
return Err(SolcError::msg(output));
}

let contracts = output.into_artifacts().fold(
HashMap::new(),
|mut contracts: HashMap<_, Vec<_>>, (id, _)| {
contracts.entry(id.name).or_default().push(id.source);
contracts
},
);

Ok(contracts)
}

/// Parses project sources via solang parser, collecting mapping from contract name to source
/// files containing artifact with given name. On parser failure, fallbacks to
/// [Self::collect_contract_names_solc].
/// Parses the sources in memory and collects all the contract names mapped to their file paths.
fn collect_contract_names(&self) -> Result<HashMap<String, Vec<PathBuf>>>
where
T: Clone,
C: Clone,
{
let graph = Graph::<C::ParsedSource>::resolve(&self.paths)?;
let mut contracts: HashMap<String, Vec<PathBuf>> = HashMap::new();

for file in graph.files().keys() {
let src = fs::read_to_string(file).map_err(|e| SolcError::io(e, file))?;
let Ok((parsed, _)) = solang_parser::parse(&src, 0) else {
return self.collect_contract_names_solc();
};

for part in parsed.0 {
if let SourceUnitPart::ContractDefinition(contract) = part {
if let Some(name) = contract.name {
contracts.entry(name.name).or_default().push(file.clone());
}
if !graph.is_empty() {
for node in graph.nodes(0) {
for contract_name in node.data.contract_names() {
contracts
.entry(contract_name.clone())
.or_default()
.push(node.path().to_path_buf());
}
}
}

Ok(contracts)
}

Expand Down
13 changes: 12 additions & 1 deletion crates/compilers/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,16 @@ impl<D: ParsedSource> Graph<D> {
!self.edges.edges[index].is_empty()
}

/// Returns all the resolved files and their index in the graph
/// Returns all the resolved files and their index in the graph.
pub fn files(&self) -> &HashMap<PathBuf, usize> {
&self.edges.indices
}

/// Returns `true` if the graph is empty.
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}

/// Gets a node by index.
///
/// # Panics
Expand Down Expand Up @@ -905,6 +910,12 @@ impl<D: ParsedSource> Node<D> {
Ok(Self { path: file.to_path_buf(), source, data })
}

/// Returns the path of the file.
pub fn path(&self) -> &Path {
&self.path
}

/// Returns the contents of the file.
pub fn content(&self) -> &str {
&self.source.content
}
Expand Down
Loading

0 comments on commit 034ecd6

Please sign in to comment.