solar_config/
lib.rs

1#![doc = include_str!("../README.md")]
2#![doc(
3    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/solar/main/assets/logo.png",
4    html_favicon_url = "https://raw.githubusercontent.com/paradigmxyz/solar/main/assets/favicon.ico"
5)]
6#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
7
8use std::{fmt, num::NonZeroUsize};
9use strum::EnumIs;
10
11#[macro_use]
12mod macros;
13
14mod opts;
15pub use opts::{Opts, UnstableOpts};
16
17mod utils;
18
19#[cfg(feature = "version")]
20pub mod version;
21
22/// Whether the target is single-threaded.
23///
24/// We still allow passing `-j` greater than 1, but it should gracefully handle the error when
25/// spawning the thread pool.
26///
27/// Modified from `libtest`: <https://github.com/rust-lang/rust/blob/96cfc75584359ae7ad11cc45968059f29e7b44b7/library/test/src/lib.rs#L605-L607>
28pub const SINGLE_THREADED_TARGET: bool =
29    cfg!(target_os = "emscripten") || cfg!(target_family = "wasm") || cfg!(target_os = "zkvm");
30
31str_enum! {
32    /// Compiler stage.
33    #[derive(strum::EnumIs)]
34    #[strum(serialize_all = "lowercase")]
35    pub enum CompilerStage {
36        /// Source code was parsed into an AST.
37        #[strum(serialize = "parsed", serialize = "parsing")]
38        Parsed,
39        // TODO: More
40    }
41}
42
43str_enum! {
44    /// Source code language.
45    #[derive(Default)]
46    #[derive(strum::EnumIs)]
47    #[strum(serialize_all = "lowercase")]
48    pub enum Language {
49        #[default]
50        Solidity,
51        Yul,
52    }
53}
54
55str_enum! {
56    /// A version specifier of the EVM we want to compile to.
57    ///
58    /// Defaults to the latest version deployed on Ethereum Mainnet at the time of compiler release.
59    #[derive(Default)]
60    #[strum(serialize_all = "camelCase")]
61    pub enum EvmVersion {
62        // NOTE: Order matters.
63        Homestead,
64        TangerineWhistle,
65        SpuriousDragon,
66        Byzantium,
67        Constantinople,
68        Petersburg,
69        Istanbul,
70        Berlin,
71        London,
72        Paris,
73        Shanghai,
74        #[default]
75        Cancun,
76        Prague,
77    }
78}
79
80impl EvmVersion {
81    pub fn supports_returndata(self) -> bool {
82        self >= Self::Byzantium
83    }
84    pub fn has_static_call(self) -> bool {
85        self >= Self::Byzantium
86    }
87    pub fn has_bitwise_shifting(self) -> bool {
88        self >= Self::Constantinople
89    }
90    pub fn has_create2(self) -> bool {
91        self >= Self::Constantinople
92    }
93    pub fn has_ext_code_hash(self) -> bool {
94        self >= Self::Constantinople
95    }
96    pub fn has_chain_id(self) -> bool {
97        self >= Self::Istanbul
98    }
99    pub fn has_self_balance(self) -> bool {
100        self >= Self::Istanbul
101    }
102    pub fn has_base_fee(self) -> bool {
103        self >= Self::London
104    }
105    pub fn has_blob_base_fee(self) -> bool {
106        self >= Self::Cancun
107    }
108    pub fn has_prev_randao(self) -> bool {
109        self >= Self::Paris
110    }
111    pub fn has_push0(self) -> bool {
112        self >= Self::Shanghai
113    }
114}
115
116str_enum! {
117    /// Type of output for the compiler to emit.
118    #[strum(serialize_all = "kebab-case")]
119    pub enum CompilerOutput {
120        /// JSON ABI.
121        Abi,
122        // /// Creation bytecode.
123        // Bin,
124        // /// Runtime bytecode.
125        // BinRuntime,
126        /// Function signature hashes.
127        Hashes,
128    }
129}
130
131/// `-Zdump=kind[=paths...]`.
132#[derive(Clone, Debug)]
133pub struct Dump {
134    pub kind: DumpKind,
135    pub paths: Option<Vec<String>>,
136}
137
138impl std::str::FromStr for Dump {
139    type Err = String;
140
141    fn from_str(s: &str) -> Result<Self, Self::Err> {
142        let (kind, paths) = if let Some((kind, paths)) = s.split_once('=') {
143            let paths = paths.split(',').map(ToString::to_string).collect();
144            (kind, Some(paths))
145        } else {
146            (s, None)
147        };
148        Ok(Self { kind: kind.parse::<DumpKind>().map_err(|e| e.to_string())?, paths })
149    }
150}
151
152str_enum! {
153    /// What kind of output to dump. See [`Dump`].
154    #[derive(EnumIs)]
155    #[strum(serialize_all = "kebab-case")]
156    pub enum DumpKind {
157        /// Print the AST.
158        Ast,
159        /// Print the HIR.
160        Hir,
161    }
162}
163
164str_enum! {
165    /// How errors and other messages are produced.
166    #[derive(Default)]
167    #[strum(serialize_all = "kebab-case")]
168    pub enum ErrorFormat {
169        /// Human-readable output.
170        #[default]
171        Human,
172        /// Solc-like JSON output.
173        Json,
174        /// Rustc-like JSON output.
175        RustcJson,
176    }
177}
178
179/// A single import remapping: `[context:]prefix=path`.
180#[derive(Clone)]
181pub struct ImportRemapping {
182    /// The remapping context, or empty string if none.
183    pub context: String,
184    pub prefix: String,
185    pub path: String,
186}
187
188impl std::str::FromStr for ImportRemapping {
189    type Err = &'static str;
190
191    fn from_str(s: &str) -> Result<Self, Self::Err> {
192        if let Some((prefix_, path)) = s.split_once('=') {
193            let (context, prefix) = prefix_.split_once(':').unzip();
194            let prefix = prefix.unwrap_or(prefix_);
195            if prefix.is_empty() {
196                return Err("empty prefix");
197            }
198            Ok(Self {
199                context: context.unwrap_or_default().into(),
200                prefix: prefix.into(),
201                path: path.into(),
202            })
203        } else {
204            Err("missing '='")
205        }
206    }
207}
208
209impl fmt::Display for ImportRemapping {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        if !self.context.is_empty() {
212            write!(f, "{}:", self.context)?;
213        }
214        write!(f, "{}={}", self.prefix, self.path)
215    }
216}
217
218impl fmt::Debug for ImportRemapping {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        write!(f, "ImportRemapping({self})")
221    }
222}
223
224/// Wrapper to implement a custom `Default` value for the number of threads.
225#[derive(Clone, Copy)]
226pub struct Threads(pub NonZeroUsize);
227
228impl From<Threads> for NonZeroUsize {
229    fn from(threads: Threads) -> Self {
230        threads.0
231    }
232}
233
234impl From<NonZeroUsize> for Threads {
235    fn from(n: NonZeroUsize) -> Self {
236        Self(n)
237    }
238}
239
240impl From<usize> for Threads {
241    fn from(n: usize) -> Self {
242        Self::resolve(n)
243    }
244}
245
246impl Default for Threads {
247    fn default() -> Self {
248        Self::resolve(if SINGLE_THREADED_TARGET { 1 } else { 8 })
249    }
250}
251
252impl std::str::FromStr for Threads {
253    type Err = <NonZeroUsize as std::str::FromStr>::Err;
254
255    fn from_str(s: &str) -> Result<Self, Self::Err> {
256        s.parse::<usize>().map(Self::resolve)
257    }
258}
259
260impl std::fmt::Display for Threads {
261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        self.0.fmt(f)
263    }
264}
265
266impl std::fmt::Debug for Threads {
267    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268        self.0.fmt(f)
269    }
270}
271
272impl Threads {
273    /// Resolves the number of threads to use.
274    pub fn resolve(n: usize) -> Self {
275        Self(
276            NonZeroUsize::new(n)
277                .or_else(|| std::thread::available_parallelism().ok())
278                .unwrap_or(NonZeroUsize::MIN),
279        )
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286    use strum::IntoEnumIterator;
287
288    #[cfg(not(feature = "serde"))]
289    use serde_json as _;
290
291    #[test]
292    fn string_enum() {
293        for value in EvmVersion::iter() {
294            let s = value.to_str();
295            assert_eq!(value.to_string(), s);
296            assert_eq!(value, s.parse().unwrap());
297
298            #[cfg(feature = "serde")]
299            {
300                let json_s = format!("\"{value}\"");
301                assert_eq!(serde_json::to_string(&value).unwrap(), json_s);
302                assert_eq!(serde_json::from_str::<EvmVersion>(&json_s).unwrap(), value);
303            }
304        }
305    }
306}