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