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
23pub const SINGLE_THREADED_TARGET: bool =
30 cfg!(target_os = "emscripten") || cfg!(target_family = "wasm") || cfg!(target_os = "zkvm");
31
32str_enum! {
33 #[derive(strum::EnumIs, strum::FromRepr)]
35 #[strum(serialize_all = "kebab-case")]
36 #[non_exhaustive]
37 pub enum CompilerStage {
38 Parsing,
42 Lowering,
46 Analysis,
50 }
51}
52
53impl CompilerStage {
54 pub fn next(self) -> Option<Self> {
56 Self::from_repr(self as usize + 1)
57 }
58
59 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 #[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 #[derive(Default)]
84 #[strum(serialize_all = "camelCase")]
85 #[non_exhaustive]
86 pub enum EvmVersion {
87 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 #[strum(serialize_all = "kebab-case")]
145 #[non_exhaustive]
146 pub enum CompilerOutput {
147 Abi,
149 Hashes,
155 }
156}
157
158#[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 #[derive(EnumIs)]
182 #[strum(serialize_all = "kebab-case")]
183 #[non_exhaustive]
184 pub enum DumpKind {
185 Ast,
187 Hir,
189 }
190}
191
192str_enum! {
193 #[derive(Default)]
195 #[strum(serialize_all = "kebab-case")]
196 #[non_exhaustive]
197 pub enum ErrorFormat {
198 #[default]
200 Human,
201 Json,
203 RustcJson,
205 }
206}
207
208str_enum! {
209 #[derive(Default)]
211 #[strum(serialize_all = "kebab-case")]
212 #[non_exhaustive]
213 pub enum HumanEmitterKind {
214 #[default]
216 Ascii,
217 Unicode,
219 Short,
221 }
222}
223
224#[derive(Clone)]
226pub struct ImportRemapping {
227 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#[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 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}