1use eyre::Result;
2use foundry_compilers::{
3 CompilerInput, Graph, Project, ProjectCompileOutput, ProjectPathsConfig,
4 artifacts::{Source, Sources},
5 multi::{MultiCompilerLanguage, MultiCompilerParser},
6 solc::{SOLC_EXTENSIONS, SolcLanguage, SolcVersionedInput},
7};
8use foundry_config::Config;
9use rayon::prelude::*;
10use solar::{interface::MIN_SOLIDITY_VERSION, sema::ParsingContext};
11use std::{
12 collections::{HashSet, VecDeque},
13 path::{Path, PathBuf},
14};
15
16pub fn configure_pcx(
24 pcx: &mut ParsingContext<'_>,
25 config: &Config,
26 project: Option<&Project>,
27 target_paths: Option<&[PathBuf]>,
28) -> Result<()> {
29 let status = configure_pcx_with_sources(pcx, config, project, target_paths, false)?;
30 if !status.has_compatible_sources && !status.has_unsupported_sources {
31 eyre::bail!("no Solidity sources");
32 }
33 Ok(())
34}
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
38pub struct SolarSourceStatus {
39 has_compatible_sources: bool,
41 has_unsupported_sources: bool,
43}
44
45impl SolarSourceStatus {
46 pub const fn is_fully_supported(&self) -> bool {
48 self.has_compatible_sources && !self.has_unsupported_sources
49 }
50}
51
52pub fn configure_pcx_all_sources(
56 pcx: &mut ParsingContext<'_>,
57 config: &Config,
58 project: Option<&Project>,
59 target_paths: Option<&[PathBuf]>,
60) -> Result<bool> {
61 let status = configure_pcx_all_sources_with_status(pcx, config, project, target_paths)?;
62 if !status.has_compatible_sources && !status.has_unsupported_sources {
63 eyre::bail!("no Solidity sources");
64 }
65 Ok(status.has_compatible_sources)
66}
67
68pub fn configure_pcx_all_sources_with_status(
71 pcx: &mut ParsingContext<'_>,
72 config: &Config,
73 project: Option<&Project>,
74 target_paths: Option<&[PathBuf]>,
75) -> Result<SolarSourceStatus> {
76 configure_pcx_with_sources(pcx, config, project, target_paths, true)
77}
78
79fn configure_pcx_with_sources(
80 pcx: &mut ParsingContext<'_>,
81 config: &Config,
82 project: Option<&Project>,
83 target_paths: Option<&[PathBuf]>,
84 all_versions: bool,
85) -> Result<SolarSourceStatus> {
86 let project = match project {
88 Some(project) => project,
89 None => &config.ephemeral_project()?,
90 };
91
92 let sources = match target_paths {
93 Some(targets) => {
95 let mut sources = Sources::new();
96 for t in targets {
97 let path = dunce::canonicalize(t)?;
98 let source = Source::read(&path)?;
99 sources.insert(path, source);
100 }
101 sources
102 }
103 None => {
105 let mut sources = project.paths.read_input_files()?;
106 if let Some(filter) = &project.sparse_output {
107 sources.retain(|path, _| filter.is_match(path));
108 }
109 sources
110 }
111 };
112
113 let graph = Graph::<MultiCompilerParser>::resolve_sources(&project.paths, sources)?;
115 let Some(versioned_sources) = graph
116 .into_sources_by_version(project)?
118 .sources
119 .into_iter()
120 .find(|(lang, _)| *lang == MultiCompilerLanguage::Solc(SolcLanguage::Solidity))
122 .map(|(_, sources)| sources)
123 else {
124 return Ok(SolarSourceStatus::default());
125 };
126
127 let mut has_unsupported_sources = false;
128 let versioned_sources = versioned_sources.into_iter().filter(|(version, sources, _)| {
129 if version < &MIN_SOLIDITY_VERSION && !sources.is_empty() {
130 has_unsupported_sources = true;
131 false
132 } else {
133 true
134 }
135 });
136 let (version, sources) = if all_versions {
137 versioned_sources.fold(
138 (MIN_SOLIDITY_VERSION, Sources::default()),
139 |(version, mut sources), (source_version, version_sources, _)| {
140 sources.extend(version_sources);
141 (version.max(source_version), sources)
142 },
143 )
144 } else {
145 versioned_sources
146 .max_by(|(v1, _, _), (v2, _, _)| v1.cmp(v2))
147 .map_or((MIN_SOLIDITY_VERSION, Sources::default()), |(v, s, _)| (v, s))
148 };
149
150 let has_sources = !sources.is_empty();
151 if !all_versions && !has_sources {
152 sh_warn!("no files found. Solar doesn't support Solidity versions prior to 0.8.0")?;
153 }
154
155 let solc = SolcVersionedInput::build(
156 sources,
157 config.solc_settings()?,
158 SolcLanguage::Solidity,
159 version,
160 );
161
162 configure_pcx_from_solc(pcx, &project.paths, &solc, true);
163
164 Ok(SolarSourceStatus { has_compatible_sources: has_sources, has_unsupported_sources })
165}
166
167pub fn get_solar_sources_from_compile_output(
174 config: &Config,
175 output: &ProjectCompileOutput,
176 target_paths: Option<&[PathBuf]>,
177 ignored_paths: Option<&[PathBuf]>,
178) -> Result<SolcVersionedInput> {
179 let is_solidity_file = |path: &Path| -> bool {
180 path.extension().and_then(|s| s.to_str()).is_some_and(|ext| SOLC_EXTENSIONS.contains(&ext))
181 };
182
183 let is_ignored = |path: &Path| -> bool {
184 if let Some(ignored) = ignored_paths {
185 ignored.iter().any(|ignored_path| path == ignored_path)
186 } else {
187 false
188 }
189 };
190
191 let mut source_paths: HashSet<PathBuf> = if let Some(targets) = target_paths
193 && !targets.is_empty()
194 {
195 let mut source_paths = HashSet::new();
196 let mut queue: VecDeque<PathBuf> = targets
197 .iter()
198 .filter_map(|path| {
199 is_solidity_file(path).then(|| dunce::canonicalize(path).ok()).flatten()
200 })
201 .collect();
202
203 while let Some(path) = queue.pop_front() {
204 if source_paths.insert(path.clone()) {
205 for import in output.graph().imports(path.as_path()) {
206 if !is_ignored(import) {
208 queue.push_back(import.to_path_buf());
209 }
210 }
211 }
212 }
213
214 source_paths
215 } else {
216 output
217 .graph()
218 .source_files()
219 .filter_map(|idx| {
220 let path = output.graph().node_path(idx).to_path_buf();
221 is_solidity_file(&path).then_some(path)
222 })
223 .collect()
224 };
225
226 let (version, sources) = {
228 let (mut max_version, mut sources) = (MIN_SOLIDITY_VERSION, Sources::new());
229 for (id, _) in output.artifact_ids() {
230 if let Ok(path) = dunce::canonicalize(&id.source)
231 && source_paths.remove(&path)
232 {
233 if id.version < MIN_SOLIDITY_VERSION {
234 continue;
235 } else if max_version < id.version {
236 max_version = id.version;
237 };
238
239 let source = Source::read(&path)?;
240 sources.insert(path, source);
241 }
242 }
243
244 (max_version, sources)
245 };
246
247 let solc = SolcVersionedInput::build(
248 sources,
249 config.solc_settings()?,
250 SolcLanguage::Solidity,
251 version,
252 );
253
254 Ok(solc)
255}
256
257pub fn configure_pcx_from_compile_output(
259 pcx: &mut ParsingContext<'_>,
260 config: &Config,
261 output: &ProjectCompileOutput,
262 target_paths: Option<&[PathBuf]>,
263) -> Result<()> {
264 let solc = get_solar_sources_from_compile_output(config, output, target_paths, None)?;
265 configure_pcx_from_solc(pcx, &config.project_paths(), &solc, true);
266 Ok(())
267}
268
269pub fn configure_pcx_from_solc(
274 pcx: &mut ParsingContext<'_>,
275 project_paths: &ProjectPathsConfig,
276 vinput: &SolcVersionedInput,
277 add_source_files: bool,
278) {
279 configure_pcx_from_solc_cli(pcx, project_paths, &vinput.cli_settings);
280 if add_source_files {
281 let sources = vinput
282 .input
283 .sources
284 .par_iter()
285 .filter_map(|(path, source)| {
286 pcx.sess.source_map().new_source_file(path.clone(), source.content.as_str()).ok()
287 })
288 .collect::<Vec<_>>();
289 pcx.add_files(sources);
290 }
291}
292
293fn configure_pcx_from_solc_cli(
294 pcx: &mut ParsingContext<'_>,
295 project_paths: &ProjectPathsConfig,
296 cli_settings: &foundry_compilers::solc::CliSettings,
297) {
298 pcx.file_resolver
299 .set_current_dir(cli_settings.base_path.as_ref().unwrap_or(&project_paths.root));
300 for remapping in &project_paths.remappings {
301 pcx.file_resolver.add_import_remapping(solar::sema::interface::config::ImportRemapping {
302 context: remapping.context.clone().unwrap_or_default(),
303 prefix: remapping.name.clone(),
304 path: remapping.path.clone(),
305 });
306 }
307 pcx.file_resolver.add_include_paths(cli_settings.include_paths.iter().cloned());
308}