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