foundry_compilers/compilers/
multi.rs

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    parser::VyperParser,
14    resolver::parse::{SolData, SolParser},
15    settings::VyperRestrictions,
16    solc::SolcRestrictions,
17    SourceParser,
18};
19use foundry_compilers_artifacts::{
20    error::SourceLocation,
21    output_selection::OutputSelection,
22    remappings::Remapping,
23    sources::{Source, Sources},
24    Contract, Error, Severity, SolcLanguage,
25};
26use foundry_compilers_core::error::{Result, SolcError};
27use semver::Version;
28use serde::{Deserialize, Serialize};
29use std::{
30    borrow::Cow,
31    collections::BTreeSet,
32    fmt,
33    path::{Path, PathBuf},
34};
35
36/// Compiler capable of compiling both Solidity and Vyper sources.
37#[derive(Clone, Debug)]
38pub struct MultiCompiler {
39    pub solc: Option<SolcCompiler>,
40    pub vyper: Option<Vyper>,
41}
42
43impl Default for MultiCompiler {
44    fn default() -> Self {
45        let vyper = Vyper::new("vyper").ok();
46
47        #[cfg(feature = "svm-solc")]
48        let solc = Some(SolcCompiler::AutoDetect);
49        #[cfg(not(feature = "svm-solc"))]
50        let solc = crate::solc::Solc::new("solc").map(SolcCompiler::Specific).ok();
51
52        Self { solc, vyper }
53    }
54}
55
56impl MultiCompiler {
57    pub fn new(solc: Option<SolcCompiler>, vyper_path: Option<PathBuf>) -> Result<Self> {
58        let vyper = vyper_path.map(Vyper::new).transpose()?;
59        Ok(Self { solc, vyper })
60    }
61}
62
63/// Languages supported by the [MultiCompiler].
64#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
65#[serde(untagged)]
66pub enum MultiCompilerLanguage {
67    Solc(SolcLanguage),
68    Vyper(VyperLanguage),
69}
70
71impl Default for MultiCompilerLanguage {
72    fn default() -> Self {
73        Self::Solc(SolcLanguage::Solidity)
74    }
75}
76
77impl MultiCompilerLanguage {
78    pub fn is_vyper(&self) -> bool {
79        matches!(self, Self::Vyper(_))
80    }
81
82    pub fn is_solc(&self) -> bool {
83        matches!(self, Self::Solc(_))
84    }
85}
86
87impl From<SolcLanguage> for MultiCompilerLanguage {
88    fn from(language: SolcLanguage) -> Self {
89        Self::Solc(language)
90    }
91}
92
93impl From<VyperLanguage> for MultiCompilerLanguage {
94    fn from(language: VyperLanguage) -> Self {
95        Self::Vyper(language)
96    }
97}
98
99impl Language for MultiCompilerLanguage {
100    const FILE_EXTENSIONS: &'static [&'static str] = &["sol", "vy", "vyi", "yul"];
101}
102
103impl fmt::Display for MultiCompilerLanguage {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            Self::Solc(lang) => lang.fmt(f),
107            Self::Vyper(lang) => lang.fmt(f),
108        }
109    }
110}
111
112/// Source parser for the [`MultiCompiler`]. Recognizes Solc and Vyper sources.
113#[derive(Clone, Debug)]
114pub struct MultiCompilerParser {
115    solc: SolParser,
116    vyper: VyperParser,
117}
118
119impl MultiCompilerParser {
120    /// Returns the parser used to parse Solc sources.
121    pub fn solc(&self) -> &SolParser {
122        &self.solc
123    }
124
125    /// Returns the parser used to parse Solc sources.
126    pub fn solc_mut(&mut self) -> &mut SolParser {
127        &mut self.solc
128    }
129
130    /// Returns the parser used to parse Vyper sources.
131    pub fn vyper(&self) -> &VyperParser {
132        &self.vyper
133    }
134
135    /// Returns the parser used to parse Vyper sources.
136    pub fn vyper_mut(&mut self) -> &mut VyperParser {
137        &mut self.vyper
138    }
139}
140
141/// Source parser for the [MultiCompiler]. Recognizes Solc and Vyper sources.
142#[derive(Clone, Debug)]
143pub enum MultiCompilerParsedSource {
144    Solc(SolData),
145    Vyper(VyperParsedSource),
146}
147
148impl MultiCompilerParsedSource {
149    fn solc(&self) -> Option<&SolData> {
150        match self {
151            Self::Solc(parsed) => Some(parsed),
152            _ => None,
153        }
154    }
155
156    fn vyper(&self) -> Option<&VyperParsedSource> {
157        match self {
158            Self::Vyper(parsed) => Some(parsed),
159            _ => None,
160        }
161    }
162}
163
164/// Compilation error which may occur when compiling Solidity or Vyper sources.
165#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
166#[serde(untagged)]
167pub enum MultiCompilerError {
168    Solc(Error),
169    Vyper(VyperCompilationError),
170}
171
172impl fmt::Display for MultiCompilerError {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        match self {
175            Self::Solc(error) => error.fmt(f),
176            Self::Vyper(error) => error.fmt(f),
177        }
178    }
179}
180
181#[derive(Clone, Copy, Debug, Default)]
182pub struct MultiCompilerRestrictions {
183    pub solc: SolcRestrictions,
184    pub vyper: VyperRestrictions,
185}
186
187impl CompilerSettingsRestrictions for MultiCompilerRestrictions {
188    fn merge(self, other: Self) -> Option<Self> {
189        Some(Self { solc: self.solc.merge(other.solc)?, vyper: self.vyper.merge(other.vyper)? })
190    }
191}
192
193/// Settings for the [MultiCompiler]. Includes settings for both Solc and Vyper compilers.
194#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
195pub struct MultiCompilerSettings {
196    pub solc: SolcSettings,
197    pub vyper: VyperSettings,
198}
199
200impl CompilerSettings for MultiCompilerSettings {
201    type Restrictions = MultiCompilerRestrictions;
202
203    fn can_use_cached(&self, other: &Self) -> bool {
204        self.solc.can_use_cached(&other.solc) && self.vyper.can_use_cached(&other.vyper)
205    }
206
207    fn update_output_selection(&mut self, f: impl FnOnce(&mut OutputSelection) + Copy) {
208        self.solc.update_output_selection(f);
209        self.vyper.update_output_selection(f);
210    }
211
212    fn with_allow_paths(self, allowed_paths: &BTreeSet<PathBuf>) -> Self {
213        Self {
214            solc: self.solc.with_allow_paths(allowed_paths),
215            vyper: self.vyper.with_allow_paths(allowed_paths),
216        }
217    }
218
219    fn with_base_path(self, base_path: &Path) -> Self {
220        Self {
221            solc: self.solc.with_base_path(base_path),
222            vyper: self.vyper.with_base_path(base_path),
223        }
224    }
225
226    fn with_include_paths(self, include_paths: &BTreeSet<PathBuf>) -> Self {
227        Self {
228            solc: self.solc.with_include_paths(include_paths),
229            vyper: self.vyper.with_include_paths(include_paths),
230        }
231    }
232
233    fn with_remappings(self, remappings: &[Remapping]) -> Self {
234        Self {
235            solc: self.solc.with_remappings(remappings),
236            vyper: self.vyper.with_remappings(remappings),
237        }
238    }
239
240    fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool {
241        self.solc.satisfies_restrictions(&restrictions.solc)
242            && self.vyper.satisfies_restrictions(&restrictions.vyper)
243    }
244}
245
246impl From<MultiCompilerSettings> for SolcSettings {
247    fn from(settings: MultiCompilerSettings) -> Self {
248        settings.solc
249    }
250}
251
252impl From<MultiCompilerSettings> for VyperSettings {
253    fn from(settings: MultiCompilerSettings) -> Self {
254        settings.vyper
255    }
256}
257
258/// Input for the [MultiCompiler]. Either Solc or Vyper input.
259#[derive(Clone, Debug, Serialize)]
260#[serde(untagged)]
261pub enum MultiCompilerInput {
262    Solc(SolcVersionedInput),
263    Vyper(VyperVersionedInput),
264}
265
266impl CompilerInput for MultiCompilerInput {
267    type Language = MultiCompilerLanguage;
268    type Settings = MultiCompilerSettings;
269
270    fn build(
271        sources: Sources,
272        settings: Self::Settings,
273        language: Self::Language,
274        version: Version,
275    ) -> Self {
276        match language {
277            MultiCompilerLanguage::Solc(language) => {
278                Self::Solc(SolcVersionedInput::build(sources, settings.solc, language, version))
279            }
280            MultiCompilerLanguage::Vyper(language) => {
281                Self::Vyper(VyperVersionedInput::build(sources, settings.vyper, language, version))
282            }
283        }
284    }
285
286    fn compiler_name(&self) -> Cow<'static, str> {
287        match self {
288            Self::Solc(input) => input.compiler_name(),
289            Self::Vyper(input) => input.compiler_name(),
290        }
291    }
292
293    fn language(&self) -> Self::Language {
294        match self {
295            Self::Solc(input) => MultiCompilerLanguage::Solc(input.language()),
296            Self::Vyper(input) => MultiCompilerLanguage::Vyper(input.language()),
297        }
298    }
299
300    fn strip_prefix(&mut self, base: &Path) {
301        match self {
302            Self::Solc(input) => input.strip_prefix(base),
303            Self::Vyper(input) => input.strip_prefix(base),
304        }
305    }
306
307    fn version(&self) -> &Version {
308        match self {
309            Self::Solc(input) => input.version(),
310            Self::Vyper(input) => input.version(),
311        }
312    }
313
314    fn sources(&self) -> impl Iterator<Item = (&Path, &Source)> {
315        let ret: Box<dyn Iterator<Item = _>> = match self {
316            Self::Solc(input) => Box::new(input.sources()),
317            Self::Vyper(input) => Box::new(input.sources()),
318        };
319
320        ret
321    }
322}
323
324impl Compiler for MultiCompiler {
325    type Input = MultiCompilerInput;
326    type CompilationError = MultiCompilerError;
327    type Parser = MultiCompilerParser;
328    type Settings = MultiCompilerSettings;
329    type Language = MultiCompilerLanguage;
330    type CompilerContract = Contract;
331
332    fn compile(
333        &self,
334        input: &Self::Input,
335    ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>> {
336        match input {
337            MultiCompilerInput::Solc(input) => {
338                if let Some(solc) = &self.solc {
339                    Compiler::compile(solc, input).map(|res| res.map_err(MultiCompilerError::Solc))
340                } else {
341                    Err(SolcError::msg("solc compiler is not available"))
342                }
343            }
344            MultiCompilerInput::Vyper(input) => {
345                if let Some(vyper) = &self.vyper {
346                    Compiler::compile(vyper, input)
347                        .map(|res| res.map_err(MultiCompilerError::Vyper))
348                } else {
349                    Err(SolcError::msg("vyper compiler is not available"))
350                }
351            }
352        }
353    }
354
355    fn available_versions(&self, language: &Self::Language) -> Vec<CompilerVersion> {
356        match language {
357            MultiCompilerLanguage::Solc(language) => {
358                self.solc.as_ref().map(|s| s.available_versions(language)).unwrap_or_default()
359            }
360            MultiCompilerLanguage::Vyper(language) => {
361                self.vyper.as_ref().map(|v| v.available_versions(language)).unwrap_or_default()
362            }
363        }
364    }
365}
366
367impl SourceParser for MultiCompilerParser {
368    type ParsedSource = MultiCompilerParsedSource;
369
370    fn new(config: &crate::ProjectPathsConfig) -> Self {
371        Self { solc: SolParser::new(config), vyper: VyperParser::new(config) }
372    }
373
374    fn read(&mut self, path: &Path) -> Result<crate::resolver::Node<Self::ParsedSource>> {
375        Ok(match guess_lang(path)? {
376            MultiCompilerLanguage::Solc(_) => {
377                self.solc.read(path)?.map_data(MultiCompilerParsedSource::Solc)
378            }
379            MultiCompilerLanguage::Vyper(_) => {
380                self.vyper.read(path)?.map_data(MultiCompilerParsedSource::Vyper)
381            }
382        })
383    }
384
385    fn parse_sources(
386        &mut self,
387        sources: &mut Sources,
388    ) -> Result<Vec<(PathBuf, crate::resolver::Node<Self::ParsedSource>)>> {
389        let mut vyper = Sources::new();
390        sources.retain(|path, source| {
391            if let Ok(lang) = guess_lang(path) {
392                match lang {
393                    MultiCompilerLanguage::Solc(_) => {}
394                    MultiCompilerLanguage::Vyper(_) => {
395                        vyper.insert(path.clone(), source.clone());
396                        return false;
397                    }
398                }
399            }
400            true
401        });
402
403        let solc_nodes = self.solc.parse_sources(sources)?;
404        let vyper_nodes = self.vyper.parse_sources(&mut vyper)?;
405        Ok(solc_nodes
406            .into_iter()
407            .map(|(k, v)| (k, v.map_data(MultiCompilerParsedSource::Solc)))
408            .chain(
409                vyper_nodes
410                    .into_iter()
411                    .map(|(k, v)| (k, v.map_data(MultiCompilerParsedSource::Vyper))),
412            )
413            .collect())
414    }
415}
416
417impl ParsedSource for MultiCompilerParsedSource {
418    type Language = MultiCompilerLanguage;
419
420    fn parse(content: &str, file: &Path) -> Result<Self> {
421        match guess_lang(file)? {
422            MultiCompilerLanguage::Solc(_) => {
423                <SolData as ParsedSource>::parse(content, file).map(Self::Solc)
424            }
425            MultiCompilerLanguage::Vyper(_) => {
426                VyperParsedSource::parse(content, file).map(Self::Vyper)
427            }
428        }
429    }
430
431    fn version_req(&self) -> Option<&semver::VersionReq> {
432        match self {
433            Self::Solc(parsed) => parsed.version_req(),
434            Self::Vyper(parsed) => parsed.version_req(),
435        }
436    }
437
438    fn contract_names(&self) -> &[String] {
439        match self {
440            Self::Solc(parsed) => parsed.contract_names(),
441            Self::Vyper(parsed) => parsed.contract_names(),
442        }
443    }
444
445    fn language(&self) -> Self::Language {
446        match self {
447            Self::Solc(parsed) => MultiCompilerLanguage::Solc(parsed.language()),
448            Self::Vyper(parsed) => MultiCompilerLanguage::Vyper(parsed.language()),
449        }
450    }
451
452    fn resolve_imports<C>(
453        &self,
454        paths: &crate::ProjectPathsConfig<C>,
455        include_paths: &mut BTreeSet<PathBuf>,
456    ) -> Result<Vec<PathBuf>> {
457        match self {
458            Self::Solc(parsed) => parsed.resolve_imports(paths, include_paths),
459            Self::Vyper(parsed) => parsed.resolve_imports(paths, include_paths),
460        }
461    }
462
463    fn compilation_dependencies<'a>(
464        &self,
465        imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
466    ) -> impl Iterator<Item = &'a Path>
467    where
468        Self: 'a,
469    {
470        match self {
471            Self::Solc(parsed) => parsed
472                .compilation_dependencies(
473                    imported_nodes.filter_map(|(path, node)| node.solc().map(|node| (path, node))),
474                )
475                .collect::<Vec<_>>(),
476            Self::Vyper(parsed) => parsed
477                .compilation_dependencies(
478                    imported_nodes.filter_map(|(path, node)| node.vyper().map(|node| (path, node))),
479                )
480                .collect::<Vec<_>>(),
481        }
482        .into_iter()
483    }
484}
485
486fn guess_lang(path: &Path) -> Result<MultiCompilerLanguage> {
487    let extension = path
488        .extension()
489        .and_then(|e| e.to_str())
490        .ok_or_else(|| SolcError::msg("failed to resolve file extension"))?;
491    if SOLC_EXTENSIONS.contains(&extension) {
492        Ok(MultiCompilerLanguage::Solc(match extension {
493            "sol" => SolcLanguage::Solidity,
494            "yul" => SolcLanguage::Yul,
495            _ => unreachable!(),
496        }))
497    } else if VYPER_EXTENSIONS.contains(&extension) {
498        Ok(MultiCompilerLanguage::Vyper(VyperLanguage::default()))
499    } else {
500        Err(SolcError::msg("unexpected file extension"))
501    }
502}
503
504impl CompilationError for MultiCompilerError {
505    fn is_warning(&self) -> bool {
506        match self {
507            Self::Solc(error) => error.is_warning(),
508            Self::Vyper(error) => error.is_warning(),
509        }
510    }
511    fn is_error(&self) -> bool {
512        match self {
513            Self::Solc(error) => error.is_error(),
514            Self::Vyper(error) => error.is_error(),
515        }
516    }
517
518    fn source_location(&self) -> Option<SourceLocation> {
519        match self {
520            Self::Solc(error) => error.source_location(),
521            Self::Vyper(error) => error.source_location(),
522        }
523    }
524
525    fn severity(&self) -> Severity {
526        match self {
527            Self::Solc(error) => error.severity(),
528            Self::Vyper(error) => error.severity(),
529        }
530    }
531
532    fn error_code(&self) -> Option<u64> {
533        match self {
534            Self::Solc(error) => error.error_code(),
535            Self::Vyper(error) => error.error_code(),
536        }
537    }
538}