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