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