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 From<SolData> for MultiCompilerParsedSource {
149    fn from(data: SolData) -> Self {
150        Self::Solc(data)
151    }
152}
153
154impl From<VyperParsedSource> for MultiCompilerParsedSource {
155    fn from(data: VyperParsedSource) -> Self {
156        Self::Vyper(data)
157    }
158}
159
160impl MultiCompilerParsedSource {
161    fn solc(&self) -> Option<&SolData> {
162        match self {
163            Self::Solc(parsed) => Some(parsed),
164            _ => None,
165        }
166    }
167
168    fn vyper(&self) -> Option<&VyperParsedSource> {
169        match self {
170            Self::Vyper(parsed) => Some(parsed),
171            _ => None,
172        }
173    }
174}
175
176/// Compilation error which may occur when compiling Solidity or Vyper sources.
177#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
178#[serde(untagged)]
179pub enum MultiCompilerError {
180    Solc(Error),
181    Vyper(VyperCompilationError),
182}
183
184impl fmt::Display for MultiCompilerError {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        match self {
187            Self::Solc(error) => error.fmt(f),
188            Self::Vyper(error) => error.fmt(f),
189        }
190    }
191}
192
193#[derive(Clone, Copy, Debug, Default)]
194pub struct MultiCompilerRestrictions {
195    pub solc: SolcRestrictions,
196    pub vyper: VyperRestrictions,
197}
198
199impl CompilerSettingsRestrictions for MultiCompilerRestrictions {
200    fn merge(self, other: Self) -> Option<Self> {
201        Some(Self { solc: self.solc.merge(other.solc)?, vyper: self.vyper.merge(other.vyper)? })
202    }
203}
204
205/// Settings for the [MultiCompiler]. Includes settings for both Solc and Vyper compilers.
206#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
207pub struct MultiCompilerSettings {
208    pub solc: SolcSettings,
209    pub vyper: VyperSettings,
210}
211
212impl CompilerSettings for MultiCompilerSettings {
213    type Restrictions = MultiCompilerRestrictions;
214
215    fn can_use_cached(&self, other: &Self) -> bool {
216        self.solc.can_use_cached(&other.solc) && self.vyper.can_use_cached(&other.vyper)
217    }
218
219    fn update_output_selection(&mut self, mut f: impl FnMut(&mut OutputSelection)) {
220        self.solc.update_output_selection(&mut f);
221        self.vyper.update_output_selection(f);
222    }
223
224    fn with_allow_paths(self, allowed_paths: &BTreeSet<PathBuf>) -> Self {
225        Self {
226            solc: self.solc.with_allow_paths(allowed_paths),
227            vyper: self.vyper.with_allow_paths(allowed_paths),
228        }
229    }
230
231    fn with_base_path(self, base_path: &Path) -> Self {
232        Self {
233            solc: self.solc.with_base_path(base_path),
234            vyper: self.vyper.with_base_path(base_path),
235        }
236    }
237
238    fn with_include_paths(self, include_paths: &BTreeSet<PathBuf>) -> Self {
239        Self {
240            solc: self.solc.with_include_paths(include_paths),
241            vyper: self.vyper.with_include_paths(include_paths),
242        }
243    }
244
245    fn with_remappings(self, remappings: &[Remapping]) -> Self {
246        Self {
247            solc: self.solc.with_remappings(remappings),
248            vyper: self.vyper.with_remappings(remappings),
249        }
250    }
251
252    fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool {
253        self.solc.satisfies_restrictions(&restrictions.solc)
254            && self.vyper.satisfies_restrictions(&restrictions.vyper)
255    }
256}
257
258impl From<MultiCompilerSettings> for SolcSettings {
259    fn from(settings: MultiCompilerSettings) -> Self {
260        settings.solc
261    }
262}
263
264impl From<MultiCompilerSettings> for VyperSettings {
265    fn from(settings: MultiCompilerSettings) -> Self {
266        settings.vyper
267    }
268}
269
270/// Input for the [MultiCompiler]. Either Solc or Vyper input.
271#[derive(Clone, Debug, Serialize)]
272#[serde(untagged)]
273pub enum MultiCompilerInput {
274    Solc(SolcVersionedInput),
275    Vyper(VyperVersionedInput),
276}
277
278impl CompilerInput for MultiCompilerInput {
279    type Language = MultiCompilerLanguage;
280    type Settings = MultiCompilerSettings;
281
282    fn build(
283        sources: Sources,
284        settings: Self::Settings,
285        language: Self::Language,
286        version: Version,
287    ) -> Self {
288        match language {
289            MultiCompilerLanguage::Solc(language) => {
290                Self::Solc(SolcVersionedInput::build(sources, settings.solc, language, version))
291            }
292            MultiCompilerLanguage::Vyper(language) => {
293                Self::Vyper(VyperVersionedInput::build(sources, settings.vyper, language, version))
294            }
295        }
296    }
297
298    fn compiler_name(&self) -> Cow<'static, str> {
299        match self {
300            Self::Solc(input) => input.compiler_name(),
301            Self::Vyper(input) => input.compiler_name(),
302        }
303    }
304
305    fn language(&self) -> Self::Language {
306        match self {
307            Self::Solc(input) => MultiCompilerLanguage::Solc(input.language()),
308            Self::Vyper(input) => MultiCompilerLanguage::Vyper(input.language()),
309        }
310    }
311
312    fn strip_prefix(&mut self, base: &Path) {
313        match self {
314            Self::Solc(input) => input.strip_prefix(base),
315            Self::Vyper(input) => input.strip_prefix(base),
316        }
317    }
318
319    fn version(&self) -> &Version {
320        match self {
321            Self::Solc(input) => input.version(),
322            Self::Vyper(input) => input.version(),
323        }
324    }
325
326    fn sources(&self) -> impl Iterator<Item = (&Path, &Source)> {
327        let ret: Box<dyn Iterator<Item = _>> = match self {
328            Self::Solc(input) => Box::new(input.sources()),
329            Self::Vyper(input) => Box::new(input.sources()),
330        };
331
332        ret
333    }
334}
335
336impl Compiler for MultiCompiler {
337    type Input = MultiCompilerInput;
338    type CompilationError = MultiCompilerError;
339    type Parser = MultiCompilerParser;
340    type Settings = MultiCompilerSettings;
341    type Language = MultiCompilerLanguage;
342    type CompilerContract = Contract;
343
344    fn compile(
345        &self,
346        input: &Self::Input,
347    ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>> {
348        match input {
349            MultiCompilerInput::Solc(input) => {
350                if let Some(solc) = &self.solc {
351                    Compiler::compile(solc, input).map(|res| res.map_err(MultiCompilerError::Solc))
352                } else {
353                    Err(SolcError::msg("solc compiler is not available"))
354                }
355            }
356            MultiCompilerInput::Vyper(input) => {
357                if let Some(vyper) = &self.vyper {
358                    Compiler::compile(vyper, input)
359                        .map(|res| res.map_err(MultiCompilerError::Vyper))
360                } else {
361                    Err(SolcError::msg("vyper compiler is not available"))
362                }
363            }
364        }
365    }
366
367    fn available_versions(&self, language: &Self::Language) -> Vec<CompilerVersion> {
368        match language {
369            MultiCompilerLanguage::Solc(language) => {
370                self.solc.as_ref().map(|s| s.available_versions(language)).unwrap_or_default()
371            }
372            MultiCompilerLanguage::Vyper(language) => {
373                self.vyper.as_ref().map(|v| v.available_versions(language)).unwrap_or_default()
374            }
375        }
376    }
377}
378
379impl SourceParser for MultiCompilerParser {
380    type ParsedSource = MultiCompilerParsedSource;
381
382    fn new(config: &crate::ProjectPathsConfig) -> Self {
383        Self { solc: SolParser::new(config), vyper: VyperParser::new(config) }
384    }
385
386    fn read(&mut self, path: &Path) -> Result<crate::resolver::Node<Self::ParsedSource>> {
387        Ok(match guess_lang(path)? {
388            MultiCompilerLanguage::Solc(_) => {
389                self.solc.read(path)?.map_data(MultiCompilerParsedSource::Solc)
390            }
391            MultiCompilerLanguage::Vyper(_) => {
392                self.vyper.read(path)?.map_data(MultiCompilerParsedSource::Vyper)
393            }
394        })
395    }
396
397    fn parse_sources(
398        &mut self,
399        sources: &mut Sources,
400    ) -> Result<Vec<(PathBuf, crate::resolver::Node<Self::ParsedSource>)>> {
401        let mut vyper = Sources::new();
402        sources.retain(|path, source| {
403            if let Ok(lang) = guess_lang(path) {
404                match lang {
405                    MultiCompilerLanguage::Solc(_) => {}
406                    MultiCompilerLanguage::Vyper(_) => {
407                        vyper.insert(path.clone(), source.clone());
408                        return false;
409                    }
410                }
411            }
412            true
413        });
414
415        let solc_nodes = self.solc.parse_sources(sources)?;
416        let vyper_nodes = self.vyper.parse_sources(&mut vyper)?;
417        Ok(solc_nodes
418            .into_iter()
419            .map(|(k, v)| (k, v.map_data(MultiCompilerParsedSource::Solc)))
420            .chain(
421                vyper_nodes
422                    .into_iter()
423                    .map(|(k, v)| (k, v.map_data(MultiCompilerParsedSource::Vyper))),
424            )
425            .collect())
426    }
427
428    fn finalize_imports(
429        &mut self,
430        all_nodes: &mut Vec<crate::resolver::Node<Self::ParsedSource>>,
431        include_paths: &BTreeSet<PathBuf>,
432    ) -> Result<()> {
433        // Must maintain original order.
434        let mut solc_nodes = Vec::new();
435        let mut vyper_nodes = Vec::new();
436        let mut order = Vec::new();
437        for node in std::mem::take(all_nodes) {
438            order.push(node.data.language());
439            match node.data {
440                MultiCompilerParsedSource::Solc(_) => {
441                    solc_nodes.push(node.map_data(|data| match data {
442                        MultiCompilerParsedSource::Solc(data) => data,
443                        _ => unreachable!(),
444                    }));
445                }
446                MultiCompilerParsedSource::Vyper(_) => {
447                    vyper_nodes.push(node.map_data(|data| match data {
448                        MultiCompilerParsedSource::Vyper(data) => data,
449                        _ => unreachable!(),
450                    }));
451                }
452            }
453        }
454
455        self.solc.finalize_imports(&mut solc_nodes, include_paths)?;
456        self.vyper.finalize_imports(&mut vyper_nodes, include_paths)?;
457
458        // Assume that the order was not changed by the parsers.
459        let mut solc_nodes = solc_nodes.into_iter();
460        let mut vyper_nodes = vyper_nodes.into_iter();
461        for lang in order {
462            match lang {
463                MultiCompilerLanguage::Solc(_) => {
464                    all_nodes.push(solc_nodes.next().unwrap().map_data(Into::into));
465                }
466                MultiCompilerLanguage::Vyper(_) => {
467                    all_nodes.push(vyper_nodes.next().unwrap().map_data(Into::into));
468                }
469            }
470        }
471        assert!(solc_nodes.next().is_none());
472        assert!(vyper_nodes.next().is_none());
473
474        Ok(())
475    }
476}
477
478impl ParsedSource for MultiCompilerParsedSource {
479    type Language = MultiCompilerLanguage;
480
481    fn parse(content: &str, file: &Path) -> Result<Self> {
482        match guess_lang(file)? {
483            MultiCompilerLanguage::Solc(_) => {
484                <SolData as ParsedSource>::parse(content, file).map(Self::Solc)
485            }
486            MultiCompilerLanguage::Vyper(_) => {
487                VyperParsedSource::parse(content, file).map(Self::Vyper)
488            }
489        }
490    }
491
492    fn version_req(&self) -> Option<&semver::VersionReq> {
493        match self {
494            Self::Solc(parsed) => parsed.version_req(),
495            Self::Vyper(parsed) => parsed.version_req(),
496        }
497    }
498
499    fn contract_names(&self) -> &[String] {
500        match self {
501            Self::Solc(parsed) => parsed.contract_names(),
502            Self::Vyper(parsed) => parsed.contract_names(),
503        }
504    }
505
506    fn language(&self) -> Self::Language {
507        match self {
508            Self::Solc(parsed) => MultiCompilerLanguage::Solc(parsed.language()),
509            Self::Vyper(parsed) => MultiCompilerLanguage::Vyper(parsed.language()),
510        }
511    }
512
513    fn resolve_imports<C>(
514        &self,
515        paths: &crate::ProjectPathsConfig<C>,
516        include_paths: &mut BTreeSet<PathBuf>,
517    ) -> Result<Vec<PathBuf>> {
518        match self {
519            Self::Solc(parsed) => parsed.resolve_imports(paths, include_paths),
520            Self::Vyper(parsed) => parsed.resolve_imports(paths, include_paths),
521        }
522    }
523
524    fn compilation_dependencies<'a>(
525        &self,
526        imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
527    ) -> impl Iterator<Item = &'a Path>
528    where
529        Self: 'a,
530    {
531        match self {
532            Self::Solc(parsed) => parsed
533                .compilation_dependencies(
534                    imported_nodes.filter_map(|(path, node)| node.solc().map(|node| (path, node))),
535                )
536                .collect::<Vec<_>>(),
537            Self::Vyper(parsed) => parsed
538                .compilation_dependencies(
539                    imported_nodes.filter_map(|(path, node)| node.vyper().map(|node| (path, node))),
540                )
541                .collect::<Vec<_>>(),
542        }
543        .into_iter()
544    }
545}
546
547fn guess_lang(path: &Path) -> Result<MultiCompilerLanguage> {
548    let extension = path
549        .extension()
550        .and_then(|e| e.to_str())
551        .ok_or_else(|| SolcError::msg("failed to resolve file extension"))?;
552    if SOLC_EXTENSIONS.contains(&extension) {
553        Ok(MultiCompilerLanguage::Solc(match extension {
554            "sol" => SolcLanguage::Solidity,
555            "yul" => SolcLanguage::Yul,
556            _ => unreachable!(),
557        }))
558    } else if VYPER_EXTENSIONS.contains(&extension) {
559        Ok(MultiCompilerLanguage::Vyper(VyperLanguage::default()))
560    } else {
561        Err(SolcError::msg("unexpected file extension"))
562    }
563}
564
565impl CompilationError for MultiCompilerError {
566    fn is_warning(&self) -> bool {
567        match self {
568            Self::Solc(error) => error.is_warning(),
569            Self::Vyper(error) => error.is_warning(),
570        }
571    }
572    fn is_error(&self) -> bool {
573        match self {
574            Self::Solc(error) => error.is_error(),
575            Self::Vyper(error) => error.is_error(),
576        }
577    }
578
579    fn source_location(&self) -> Option<SourceLocation> {
580        match self {
581            Self::Solc(error) => error.source_location(),
582            Self::Vyper(error) => error.source_location(),
583        }
584    }
585
586    fn severity(&self) -> Severity {
587        match self {
588            Self::Solc(error) => error.severity(),
589            Self::Vyper(error) => error.severity(),
590        }
591    }
592
593    fn error_code(&self) -> Option<u64> {
594        match self {
595            Self::Solc(error) => error.error_code(),
596            Self::Vyper(error) => error.error_code(),
597        }
598    }
599}