Skip to content

Commit 4d5e52c

Browse files
DaniPopesonbjerg
andauthored
feat: add SourceParser (#300)
Allows persisting a shared [`solar_sema::Compiler`](paradigmxyz/solar#397) throughout compilation and storing it inside of the output. --------- Co-authored-by: onbjerg <[email protected]>
1 parent c3ec28c commit 4d5e52c

File tree

20 files changed

+763
-323
lines changed

20 files changed

+763
-323
lines changed

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ semver = { version = "1.0", features = ["serde"] }
5454
serde = { version = "1", features = ["derive", "rc"] }
5555
serde_json = "1.0"
5656
similar-asserts = "1"
57-
solar-parse = { version = "=0.1.5", default-features = false }
58-
solar-sema = { version = "=0.1.5", default-features = false }
57+
solar-parse = { version = "=0.1.6", default-features = false }
58+
solar-sema = { version = "=0.1.6", default-features = false }
5959
svm = { package = "svm-rs", version = "0.5", default-features = false }
6060
tempfile = "3.20"
6161
thiserror = "2"
@@ -69,7 +69,7 @@ tokio = { version = "1.47", features = ["rt-multi-thread"] }
6969

7070
snapbox = "0.6.21"
7171

72-
# [patch.crates-io]
72+
[patch.crates-io]
7373
# solar-parse = { git = "https://github.com/paradigmxyz/solar", branch = "main" }
7474
# solar-sema = { git = "https://github.com/paradigmxyz/solar", branch = "main" }
7575
# solar-ast = { git = "https://github.com/paradigmxyz/solar", branch = "main" }

crates/artifacts/solc/src/sources.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use foundry_compilers_core::error::SolcIoError;
1+
use foundry_compilers_core::error::{SolcError, SolcIoError};
22
use serde::{Deserialize, Serialize};
33
use std::{
44
collections::BTreeMap,
@@ -137,6 +137,30 @@ impl Source {
137137
Ok(Self::new(content))
138138
}
139139

140+
/// [`read`](Self::read) + mapping error to [`SolcError`].
141+
pub fn read_(file: &Path) -> Result<Self, SolcError> {
142+
Self::read(file).map_err(|err| {
143+
let exists = err.path().exists();
144+
if !exists && err.path().is_symlink() {
145+
return SolcError::ResolveBadSymlink(err);
146+
}
147+
148+
// This is an additional check useful on OS that have case-sensitive paths,
149+
// see also <https://docs.soliditylang.org/en/v0.8.17/path-resolution.html#import-callback>
150+
// check if there exists a file with different case
151+
#[cfg(feature = "walkdir")]
152+
if !exists {
153+
if let Some(existing_file) =
154+
foundry_compilers_core::utils::find_case_sensitive_existing_file(file)
155+
{
156+
return SolcError::ResolveCaseSensitiveFileName { error: err, existing_file };
157+
}
158+
}
159+
160+
SolcError::Resolve(err)
161+
})
162+
}
163+
140164
/// Returns `true` if the source should be compiled with full output selection.
141165
pub fn is_dirty(&self) -> bool {
142166
self.kind.is_dirty()

crates/compilers/src/cache.rs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
output::Builds,
77
resolver::GraphEdges,
88
ArtifactFile, ArtifactOutput, Artifacts, ArtifactsMap, Graph, OutputContext, Project,
9-
ProjectPaths, ProjectPathsConfig, SourceCompilationKind,
9+
ProjectPaths, ProjectPathsConfig, SourceCompilationKind, SourceParser,
1010
};
1111
use foundry_compilers_artifacts::{
1212
sources::{Source, Sources},
@@ -658,7 +658,7 @@ pub(crate) struct ArtifactsCacheInner<
658658
pub cached_builds: Builds<C::Language>,
659659

660660
/// Relationship between all the files.
661-
pub edges: GraphEdges<C::ParsedSource>,
661+
pub edges: GraphEdges<C::Parser>,
662662

663663
/// The project.
664664
pub project: &'a Project<C, T>,
@@ -723,6 +723,7 @@ impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
723723
/// Gets or calculates the interface representation hash for the given source file.
724724
fn interface_repr_hash(&mut self, source: &Source, file: &Path) -> &str {
725725
self.interface_repr_hashes.entry(file.to_path_buf()).or_insert_with(|| {
726+
// TODO: use `interface_representation_ast` directly with `edges.parser()`.
726727
if let Some(r) = interface_repr_hash(&source.content, file) {
727728
return r;
728729
}
@@ -823,10 +824,10 @@ impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
823824

824825
// Walks over all cache entries, detects dirty files and removes them from cache.
825826
fn find_and_remove_dirty(&mut self) {
826-
fn populate_dirty_files<D>(
827+
fn populate_dirty_files<P: SourceParser>(
827828
file: &Path,
828829
dirty_files: &mut HashSet<PathBuf>,
829-
edges: &GraphEdges<D>,
830+
edges: &GraphEdges<P>,
830831
) {
831832
for file in edges.importers(file) {
832833
// If file is marked as dirty we either have already visited it or it was marked as
@@ -890,7 +891,7 @@ impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
890891

891892
// Build a temporary graph for walking imports. We need this because `self.edges`
892893
// only contains graph data for in-scope sources but we are operating on cache entries.
893-
if let Ok(graph) = Graph::<C::ParsedSource>::resolve_sources(&self.project.paths, sources) {
894+
if let Ok(graph) = Graph::<C::Parser>::resolve_sources(&self.project.paths, sources) {
894895
let (sources, edges) = graph.into_sources();
895896

896897
// Calculate content hashes for later comparison.
@@ -1020,7 +1021,7 @@ pub(crate) enum ArtifactsCache<
10201021
C: Compiler,
10211022
> {
10221023
/// Cache nothing on disk
1023-
Ephemeral(GraphEdges<C::ParsedSource>, &'a Project<C, T>),
1024+
Ephemeral(GraphEdges<C::Parser>, &'a Project<C, T>),
10241025
/// Handles the actual cached artifacts, detects artifacts that can be reused
10251026
Cached(ArtifactsCacheInner<'a, T, C>),
10261027
}
@@ -1032,7 +1033,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
10321033
#[instrument(name = "ArtifactsCache::new", skip(project, edges))]
10331034
pub fn new(
10341035
project: &'a Project<C, T>,
1035-
edges: GraphEdges<C::ParsedSource>,
1036+
edges: GraphEdges<C::Parser>,
10361037
preprocessed: bool,
10371038
) -> Result<Self> {
10381039
/// Returns the [CompilerCache] to use
@@ -1117,7 +1118,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
11171118
}
11181119

11191120
/// Returns the graph data for this project
1120-
pub fn graph(&self) -> &GraphEdges<C::ParsedSource> {
1121+
pub fn graph(&self) -> &GraphEdges<C::Parser> {
11211122
match self {
11221123
ArtifactsCache::Ephemeral(graph, _) => graph,
11231124
ArtifactsCache::Cached(inner) => &inner.edges,
@@ -1191,18 +1192,22 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
11911192
///
11921193
/// Returns all the _cached_ artifacts.
11931194
#[instrument(name = "ArtifactsCache::consume", skip_all)]
1195+
#[allow(clippy::type_complexity)]
11941196
pub fn consume<A>(
11951197
self,
11961198
written_artifacts: &Artifacts<A>,
11971199
written_build_infos: &Vec<RawBuildInfo<C::Language>>,
11981200
write_to_disk: bool,
1199-
) -> Result<(Artifacts<A>, Builds<C::Language>)>
1201+
) -> Result<(Artifacts<A>, Builds<C::Language>, GraphEdges<C::Parser>)>
12001202
where
12011203
T: ArtifactOutput<Artifact = A>,
12021204
{
1203-
let ArtifactsCache::Cached(cache) = self else {
1204-
trace!("no cache configured, ephemeral");
1205-
return Ok(Default::default());
1205+
let cache = match self {
1206+
ArtifactsCache::Ephemeral(edges, _project) => {
1207+
trace!("no cache configured, ephemeral");
1208+
return Ok((Default::default(), Default::default(), edges));
1209+
}
1210+
ArtifactsCache::Cached(cache) => cache,
12061211
};
12071212

12081213
let ArtifactsCacheInner {
@@ -1212,7 +1217,9 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
12121217
dirty_sources,
12131218
sources_in_scope,
12141219
project,
1215-
..
1220+
edges,
1221+
content_hashes: _,
1222+
interface_repr_hashes: _,
12161223
} = cache;
12171224

12181225
// Remove cached artifacts which are out of scope, dirty or appear in `written_artifacts`.
@@ -1264,7 +1271,7 @@ impl<'a, T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
12641271
cache.write(project.cache_path())?;
12651272
}
12661273

1267-
Ok((cached_artifacts, cached_builds))
1274+
Ok((cached_artifacts, cached_builds, edges))
12681275
}
12691276

12701277
/// Marks the cached entry as seen by the compiler, if it's cached.

crates/compilers/src/cache/iface.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{parse_one_source, replace_source_content};
2-
use solar_sema::{
2+
use solar_parse::{
33
ast::{self, Span},
44
interface::diagnostics::EmittedDiagnostics,
55
};
@@ -11,7 +11,7 @@ pub(crate) fn interface_repr_hash(content: &str, path: &Path) -> Option<String>
1111
}
1212

1313
pub(crate) fn interface_repr(content: &str, path: &Path) -> Result<String, EmittedDiagnostics> {
14-
parse_one_source(content, path, |ast| interface_representation_ast(content, &ast))
14+
parse_one_source(content, path, |sess, ast| interface_representation_ast(content, sess, ast))
1515
}
1616

1717
/// Helper function to remove parts of the contract which do not alter its interface:
@@ -21,6 +21,7 @@ pub(crate) fn interface_repr(content: &str, path: &Path) -> Result<String, Emitt
2121
/// Preserves all libraries and interfaces.
2222
pub(crate) fn interface_representation_ast(
2323
content: &str,
24+
sess: &solar_sema::interface::Session,
2425
ast: &solar_parse::ast::SourceUnit<'_>,
2526
) -> String {
2627
let mut spans_to_remove: Vec<Span> = Vec::new();
@@ -57,9 +58,9 @@ pub(crate) fn interface_representation_ast(
5758
}
5859
}
5960
}
60-
let content =
61-
replace_source_content(content, spans_to_remove.iter().map(|span| (span.to_range(), "")))
62-
.replace("\n", "");
61+
let updates =
62+
spans_to_remove.iter().map(|&span| (sess.source_map().span_to_source(span).unwrap().1, ""));
63+
let content = replace_source_content(content, updates).replace("\n", "");
6364
crate::utils::RE_TWO_OR_MORE_SPACES.replace_all(&content, "").into_owned()
6465
}
6566

crates/compilers/src/compile/output/mod.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::{
1919
compilers::{
2020
multi::MultiCompiler, CompilationError, Compiler, CompilerContract, CompilerOutput,
2121
},
22+
resolver::GraphEdges,
2223
Artifact, ArtifactId, ArtifactOutput, Artifacts, ConfigurableArtifacts,
2324
};
2425

@@ -62,7 +63,7 @@ impl<L> IntoIterator for Builds<L> {
6263

6364
/// Contains a mixture of already compiled/cached artifacts and the input set of sources that still
6465
/// need to be compiled.
65-
#[derive(Clone, Debug, Default, PartialEq, Eq)]
66+
#[derive(Clone, Debug, Default)]
6667
pub struct ProjectCompileOutput<
6768
C: Compiler = MultiCompiler,
6869
T: ArtifactOutput<CompilerContract = C::CompilerContract> = ConfigurableArtifacts,
@@ -81,11 +82,23 @@ pub struct ProjectCompileOutput<
8182
pub(crate) compiler_severity_filter: Severity,
8283
/// all build infos that were just compiled
8384
pub(crate) builds: Builds<C::Language>,
85+
/// The relationship between the source files and their imports
86+
pub(crate) edges: GraphEdges<C::Parser>,
8487
}
8588

8689
impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
8790
ProjectCompileOutput<C, T>
8891
{
92+
/// Returns the parser used to parse the sources.
93+
pub fn parser(&self) -> &C::Parser {
94+
self.edges.parser()
95+
}
96+
97+
/// Returns the parser used to parse the sources.
98+
pub fn parser_mut(&mut self) -> &mut C::Parser {
99+
self.edges.parser_mut()
100+
}
101+
89102
/// Converts all `\\` separators in _all_ paths to `/`
90103
#[instrument(skip_all)]
91104
pub fn slash_paths(&mut self) {
@@ -460,6 +473,11 @@ impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
460473
pub fn builds(&self) -> impl Iterator<Item = (&String, &BuildContext<C::Language>)> {
461474
self.builds.iter()
462475
}
476+
477+
/// Returns the source graph of the project.
478+
pub fn graph(&self) -> &GraphEdges<C::Parser> {
479+
&self.edges
480+
}
463481
}
464482

465483
impl<C: Compiler, T: ArtifactOutput<CompilerContract = C::CompilerContract>>

crates/compilers/src/compile/project.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ pub struct ProjectCompiler<
146146
C: Compiler,
147147
> {
148148
/// Contains the relationship of the source files and their imports
149-
edges: GraphEdges<C::ParsedSource>,
149+
edges: GraphEdges<C::Parser>,
150150
project: &'a Project<C, T>,
151151
/// A mapping from a source file path to the primary profile name selected for it.
152152
primary_profiles: HashMap<PathBuf, &'a str>,
@@ -381,7 +381,7 @@ impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
381381
let skip_write_to_disk = project.no_artifacts || has_error;
382382
trace!(has_error, project.no_artifacts, skip_write_to_disk, cache_path=?project.cache_path(),"prepare writing cache file");
383383

384-
let (cached_artifacts, cached_builds) =
384+
let (cached_artifacts, cached_builds, edges) =
385385
cache.consume(&compiled_artifacts, &output.build_infos, !skip_write_to_disk)?;
386386

387387
project.artifacts_handler().handle_cached_artifacts(&cached_artifacts)?;
@@ -404,6 +404,7 @@ impl<T: ArtifactOutput<CompilerContract = C::CompilerContract>, C: Compiler>
404404
ignored_file_paths,
405405
compiler_severity_filter,
406406
builds,
407+
edges,
407408
})
408409
}
409410
}

crates/compilers/src/compilers/mod.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::ProjectPathsConfig;
1+
use crate::{resolver::Node, ProjectPathsConfig};
22
use alloy_json_abi::JsonAbi;
33
use core::fmt;
44
use foundry_compilers_artifacts::{
@@ -9,6 +9,7 @@ use foundry_compilers_artifacts::{
99
BytecodeObject, CompactContractRef, Contract, FileToContractsMap, Severity, SourceFile,
1010
};
1111
use foundry_compilers_core::error::Result;
12+
use rayon::prelude::*;
1213
use semver::{Version, VersionReq};
1314
use serde::{de::DeserializeOwned, Deserialize, Serialize};
1415
use std::{
@@ -139,12 +140,40 @@ pub trait CompilerInput: Serialize + Send + Sync + Sized + Debug {
139140
fn strip_prefix(&mut self, base: &Path);
140141
}
141142

143+
/// [`ParsedSource`] parser.
144+
pub trait SourceParser: Clone + Debug + Send + Sync {
145+
type ParsedSource: ParsedSource;
146+
147+
/// Creates a new parser for the given config.
148+
fn new(config: &ProjectPathsConfig) -> Self;
149+
150+
/// Reads and parses the source file at the given path.
151+
fn read(&mut self, path: &Path) -> Result<Node<Self::ParsedSource>> {
152+
Node::read(path)
153+
}
154+
155+
/// Parses the sources in the given sources map.
156+
fn parse_sources(
157+
&mut self,
158+
sources: &mut Sources,
159+
) -> Result<Vec<(PathBuf, Node<Self::ParsedSource>)>> {
160+
sources
161+
.0
162+
.par_iter()
163+
.map(|(path, source)| {
164+
let data = Self::ParsedSource::parse(source.as_ref(), path)?;
165+
Ok((path.clone(), Node::new(path.clone(), source.clone(), data)))
166+
})
167+
.collect::<Result<_>>()
168+
}
169+
}
170+
142171
/// Parser of the source files which is used to identify imports and version requirements of the
143172
/// given source.
144173
///
145174
/// Used by path resolver to resolve imports or determine compiler versions needed to compiler given
146175
/// sources.
147-
pub trait ParsedSource: Debug + Sized + Send + Clone {
176+
pub trait ParsedSource: Clone + Debug + Sized + Send {
148177
type Language: Language;
149178

150179
/// Parses the content of the source file.
@@ -331,7 +360,7 @@ pub trait Compiler: Send + Sync + Clone {
331360
/// Output data for each contract
332361
type CompilerContract: CompilerContract;
333362
/// Source parser used for resolving imports and version requirements.
334-
type ParsedSource: ParsedSource<Language = Self::Language>;
363+
type Parser: SourceParser<ParsedSource: ParsedSource<Language = Self::Language>>;
335364
/// Compiler settings.
336365
type Settings: CompilerSettings;
337366
/// Enum of languages supported by the compiler.

0 commit comments

Comments
 (0)