Skip to main content

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