1use super::{
2 restrictions::CompilerSettingsRestrictions,
3 solc::{SolcCompiler, SolcSettings, SolcVersionedInput, SOLC_EXTENSIONS},
4 vyper::{
5 input::VyperVersionedInput, parser::VyperParsedSource, Vyper, VyperLanguage,
6 VYPER_EXTENSIONS,
7 },
8 CompilationError, Compiler, CompilerInput, CompilerOutput, CompilerSettings, CompilerVersion,
9 Language, ParsedSource,
10};
11use crate::{
12 artifacts::vyper::{VyperCompilationError, VyperSettings},
13 resolver::parse::SolData,
14 settings::VyperRestrictions,
15 solc::SolcRestrictions,
16};
17use foundry_compilers_artifacts::{
18 error::SourceLocation,
19 output_selection::OutputSelection,
20 remappings::Remapping,
21 sources::{Source, Sources},
22 Contract, Error, Severity, SolcLanguage,
23};
24use foundry_compilers_core::error::{Result, SolcError};
25use semver::Version;
26use serde::{Deserialize, Serialize};
27use std::{
28 borrow::Cow,
29 collections::BTreeSet,
30 fmt,
31 path::{Path, PathBuf},
32};
33
34#[derive(Clone, Debug)]
36pub struct MultiCompiler {
37 pub solc: Option<SolcCompiler>,
38 pub vyper: Option<Vyper>,
39}
40
41impl Default for MultiCompiler {
42 fn default() -> Self {
43 let vyper = Vyper::new("vyper").ok();
44
45 #[cfg(feature = "svm-solc")]
46 let solc = Some(SolcCompiler::AutoDetect);
47 #[cfg(not(feature = "svm-solc"))]
48 let solc = crate::solc::Solc::new("solc").map(SolcCompiler::Specific).ok();
49
50 Self { solc, vyper }
51 }
52}
53
54impl MultiCompiler {
55 pub fn new(solc: Option<SolcCompiler>, vyper_path: Option<PathBuf>) -> Result<Self> {
56 let vyper = vyper_path.map(Vyper::new).transpose()?;
57 Ok(Self { solc, vyper })
58 }
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
63#[serde(untagged)]
64pub enum MultiCompilerLanguage {
65 Solc(SolcLanguage),
66 Vyper(VyperLanguage),
67}
68
69impl MultiCompilerLanguage {
70 pub fn is_vyper(&self) -> bool {
71 matches!(self, Self::Vyper(_))
72 }
73
74 pub fn is_solc(&self) -> bool {
75 matches!(self, Self::Solc(_))
76 }
77}
78
79impl From<SolcLanguage> for MultiCompilerLanguage {
80 fn from(language: SolcLanguage) -> Self {
81 Self::Solc(language)
82 }
83}
84
85impl From<VyperLanguage> for MultiCompilerLanguage {
86 fn from(language: VyperLanguage) -> Self {
87 Self::Vyper(language)
88 }
89}
90
91impl Language for MultiCompilerLanguage {
92 const FILE_EXTENSIONS: &'static [&'static str] = &["sol", "vy", "vyi", "yul"];
93}
94
95impl fmt::Display for MultiCompilerLanguage {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self {
98 Self::Solc(lang) => lang.fmt(f),
99 Self::Vyper(lang) => lang.fmt(f),
100 }
101 }
102}
103
104#[derive(Clone, Debug)]
106pub enum MultiCompilerParsedSource {
107 Solc(SolData),
108 Vyper(VyperParsedSource),
109}
110
111impl MultiCompilerParsedSource {
112 fn solc(&self) -> Option<&SolData> {
113 match self {
114 Self::Solc(parsed) => Some(parsed),
115 _ => None,
116 }
117 }
118
119 fn vyper(&self) -> Option<&VyperParsedSource> {
120 match self {
121 Self::Vyper(parsed) => Some(parsed),
122 _ => None,
123 }
124 }
125}
126
127#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
129#[serde(untagged)]
130pub enum MultiCompilerError {
131 Solc(Error),
132 Vyper(VyperCompilationError),
133}
134
135impl fmt::Display for MultiCompilerError {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 match self {
138 Self::Solc(error) => error.fmt(f),
139 Self::Vyper(error) => error.fmt(f),
140 }
141 }
142}
143
144#[derive(Clone, Copy, Debug, Default)]
145pub struct MultiCompilerRestrictions {
146 pub solc: SolcRestrictions,
147 pub vyper: VyperRestrictions,
148}
149
150impl CompilerSettingsRestrictions for MultiCompilerRestrictions {
151 fn merge(self, other: Self) -> Option<Self> {
152 Some(Self { solc: self.solc.merge(other.solc)?, vyper: self.vyper.merge(other.vyper)? })
153 }
154}
155
156#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
158pub struct MultiCompilerSettings {
159 pub solc: SolcSettings,
160 pub vyper: VyperSettings,
161}
162
163impl CompilerSettings for MultiCompilerSettings {
164 type Restrictions = MultiCompilerRestrictions;
165
166 fn can_use_cached(&self, other: &Self) -> bool {
167 self.solc.can_use_cached(&other.solc) && self.vyper.can_use_cached(&other.vyper)
168 }
169
170 fn update_output_selection(&mut self, f: impl FnOnce(&mut OutputSelection) + Copy) {
171 self.solc.update_output_selection(f);
172 self.vyper.update_output_selection(f);
173 }
174
175 fn with_allow_paths(self, allowed_paths: &BTreeSet<PathBuf>) -> Self {
176 Self {
177 solc: self.solc.with_allow_paths(allowed_paths),
178 vyper: self.vyper.with_allow_paths(allowed_paths),
179 }
180 }
181
182 fn with_base_path(self, base_path: &Path) -> Self {
183 Self {
184 solc: self.solc.with_base_path(base_path),
185 vyper: self.vyper.with_base_path(base_path),
186 }
187 }
188
189 fn with_include_paths(self, include_paths: &BTreeSet<PathBuf>) -> Self {
190 Self {
191 solc: self.solc.with_include_paths(include_paths),
192 vyper: self.vyper.with_include_paths(include_paths),
193 }
194 }
195
196 fn with_remappings(self, remappings: &[Remapping]) -> Self {
197 Self {
198 solc: self.solc.with_remappings(remappings),
199 vyper: self.vyper.with_remappings(remappings),
200 }
201 }
202
203 fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool {
204 self.solc.satisfies_restrictions(&restrictions.solc)
205 && self.vyper.satisfies_restrictions(&restrictions.vyper)
206 }
207}
208
209impl From<MultiCompilerSettings> for SolcSettings {
210 fn from(settings: MultiCompilerSettings) -> Self {
211 settings.solc
212 }
213}
214
215impl From<MultiCompilerSettings> for VyperSettings {
216 fn from(settings: MultiCompilerSettings) -> Self {
217 settings.vyper
218 }
219}
220
221#[derive(Clone, Debug, Serialize)]
223#[serde(untagged)]
224pub enum MultiCompilerInput {
225 Solc(SolcVersionedInput),
226 Vyper(VyperVersionedInput),
227}
228
229impl CompilerInput for MultiCompilerInput {
230 type Language = MultiCompilerLanguage;
231 type Settings = MultiCompilerSettings;
232
233 fn build(
234 sources: Sources,
235 settings: Self::Settings,
236 language: Self::Language,
237 version: Version,
238 ) -> Self {
239 match language {
240 MultiCompilerLanguage::Solc(language) => {
241 Self::Solc(SolcVersionedInput::build(sources, settings.solc, language, version))
242 }
243 MultiCompilerLanguage::Vyper(language) => {
244 Self::Vyper(VyperVersionedInput::build(sources, settings.vyper, language, version))
245 }
246 }
247 }
248
249 fn compiler_name(&self) -> Cow<'static, str> {
250 match self {
251 Self::Solc(input) => input.compiler_name(),
252 Self::Vyper(input) => input.compiler_name(),
253 }
254 }
255
256 fn language(&self) -> Self::Language {
257 match self {
258 Self::Solc(input) => MultiCompilerLanguage::Solc(input.language()),
259 Self::Vyper(input) => MultiCompilerLanguage::Vyper(input.language()),
260 }
261 }
262
263 fn strip_prefix(&mut self, base: &Path) {
264 match self {
265 Self::Solc(input) => input.strip_prefix(base),
266 Self::Vyper(input) => input.strip_prefix(base),
267 }
268 }
269
270 fn version(&self) -> &Version {
271 match self {
272 Self::Solc(input) => input.version(),
273 Self::Vyper(input) => input.version(),
274 }
275 }
276
277 fn sources(&self) -> impl Iterator<Item = (&Path, &Source)> {
278 let ret: Box<dyn Iterator<Item = _>> = match self {
279 Self::Solc(input) => Box::new(input.sources()),
280 Self::Vyper(input) => Box::new(input.sources()),
281 };
282
283 ret
284 }
285}
286
287impl Compiler for MultiCompiler {
288 type Input = MultiCompilerInput;
289 type CompilationError = MultiCompilerError;
290 type ParsedSource = MultiCompilerParsedSource;
291 type Settings = MultiCompilerSettings;
292 type Language = MultiCompilerLanguage;
293 type CompilerContract = Contract;
294
295 fn compile(
296 &self,
297 input: &Self::Input,
298 ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>> {
299 match input {
300 MultiCompilerInput::Solc(input) => {
301 if let Some(solc) = &self.solc {
302 Compiler::compile(solc, input).map(|res| res.map_err(MultiCompilerError::Solc))
303 } else {
304 Err(SolcError::msg("solc compiler is not available"))
305 }
306 }
307 MultiCompilerInput::Vyper(input) => {
308 if let Some(vyper) = &self.vyper {
309 Compiler::compile(vyper, input)
310 .map(|res| res.map_err(MultiCompilerError::Vyper))
311 } else {
312 Err(SolcError::msg("vyper compiler is not available"))
313 }
314 }
315 }
316 }
317
318 fn available_versions(&self, language: &Self::Language) -> Vec<CompilerVersion> {
319 match language {
320 MultiCompilerLanguage::Solc(language) => {
321 self.solc.as_ref().map(|s| s.available_versions(language)).unwrap_or_default()
322 }
323 MultiCompilerLanguage::Vyper(language) => {
324 self.vyper.as_ref().map(|v| v.available_versions(language)).unwrap_or_default()
325 }
326 }
327 }
328}
329
330impl ParsedSource for MultiCompilerParsedSource {
331 type Language = MultiCompilerLanguage;
332
333 fn parse(content: &str, file: &std::path::Path) -> Result<Self> {
334 let Some(extension) = file.extension().and_then(|e| e.to_str()) else {
335 return Err(SolcError::msg("failed to resolve file extension"));
336 };
337
338 if SOLC_EXTENSIONS.contains(&extension) {
339 <SolData as ParsedSource>::parse(content, file).map(Self::Solc)
340 } else if VYPER_EXTENSIONS.contains(&extension) {
341 VyperParsedSource::parse(content, file).map(Self::Vyper)
342 } else {
343 Err(SolcError::msg("unexpected file extension"))
344 }
345 }
346
347 fn version_req(&self) -> Option<&semver::VersionReq> {
348 match self {
349 Self::Solc(parsed) => parsed.version_req(),
350 Self::Vyper(parsed) => parsed.version_req(),
351 }
352 }
353
354 fn contract_names(&self) -> &[String] {
355 match self {
356 Self::Solc(parsed) => parsed.contract_names(),
357 Self::Vyper(parsed) => parsed.contract_names(),
358 }
359 }
360
361 fn language(&self) -> Self::Language {
362 match self {
363 Self::Solc(parsed) => MultiCompilerLanguage::Solc(parsed.language()),
364 Self::Vyper(parsed) => MultiCompilerLanguage::Vyper(parsed.language()),
365 }
366 }
367
368 fn resolve_imports<C>(
369 &self,
370 paths: &crate::ProjectPathsConfig<C>,
371 include_paths: &mut BTreeSet<PathBuf>,
372 ) -> Result<Vec<PathBuf>> {
373 match self {
374 Self::Solc(parsed) => parsed.resolve_imports(paths, include_paths),
375 Self::Vyper(parsed) => parsed.resolve_imports(paths, include_paths),
376 }
377 }
378
379 fn compilation_dependencies<'a>(
380 &self,
381 imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
382 ) -> impl Iterator<Item = &'a Path>
383 where
384 Self: 'a,
385 {
386 match self {
387 Self::Solc(parsed) => parsed
388 .compilation_dependencies(
389 imported_nodes.filter_map(|(path, node)| node.solc().map(|node| (path, node))),
390 )
391 .collect::<Vec<_>>(),
392 Self::Vyper(parsed) => parsed
393 .compilation_dependencies(
394 imported_nodes.filter_map(|(path, node)| node.vyper().map(|node| (path, node))),
395 )
396 .collect::<Vec<_>>(),
397 }
398 .into_iter()
399 }
400}
401
402impl CompilationError for MultiCompilerError {
403 fn is_warning(&self) -> bool {
404 match self {
405 Self::Solc(error) => error.is_warning(),
406 Self::Vyper(error) => error.is_warning(),
407 }
408 }
409 fn is_error(&self) -> bool {
410 match self {
411 Self::Solc(error) => error.is_error(),
412 Self::Vyper(error) => error.is_error(),
413 }
414 }
415
416 fn source_location(&self) -> Option<SourceLocation> {
417 match self {
418 Self::Solc(error) => error.source_location(),
419 Self::Vyper(error) => error.source_location(),
420 }
421 }
422
423 fn severity(&self) -> Severity {
424 match self {
425 Self::Solc(error) => error.severity(),
426 Self::Vyper(error) => error.severity(),
427 }
428 }
429
430 fn error_code(&self) -> Option<u64> {
431 match self {
432 Self::Solc(error) => error.error_code(),
433 Self::Vyper(error) => error.error_code(),
434 }
435 }
436}