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    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/// Compiler capable of compiling both Solidity and Vyper sources.
35#[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/// Languages supported by the [MultiCompiler].
62#[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/// Source parser for the [MultiCompiler]. Recognizes Solc and Vyper sources.
105#[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/// Compilation error which may occur when compiling Solidity or Vyper sources.
128#[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/// Settings for the [MultiCompiler]. Includes settings for both Solc and Vyper compilers.
157#[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/// Input for the [MultiCompiler]. Either Solc or Vyper input.
222#[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}