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
22pub const SINGLE_THREADED_TARGET: bool =
29 cfg!(target_os = "emscripten") || cfg!(target_family = "wasm") || cfg!(target_os = "zkvm");
30
31str_enum! {
32 #[derive(strum::EnumIs, strum::FromRepr)]
34 #[strum(serialize_all = "kebab-case")]
35 #[non_exhaustive]
36 pub enum CompilerStage {
37 Parsing,
41 Lowering,
45 Analysis,
49 }
50}
51
52impl CompilerStage {
53 pub fn next(self) -> Option<Self> {
55 Self::from_repr(self as usize + 1)
56 }
57
58 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 #[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 #[derive(Default)]
83 #[strum(serialize_all = "camelCase")]
84 #[non_exhaustive]
85 pub enum EvmVersion {
86 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 #[strum(serialize_all = "kebab-case")]
144 #[non_exhaustive]
145 pub enum CompilerOutput {
146 Abi,
148 Hashes,
154 }
155}
156
157#[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 #[derive(EnumIs)]
181 #[strum(serialize_all = "kebab-case")]
182 #[non_exhaustive]
183 pub enum DumpKind {
184 Ast,
186 Hir,
188 }
189}
190
191str_enum! {
192 #[derive(Default)]
194 #[strum(serialize_all = "kebab-case")]
195 #[non_exhaustive]
196 pub enum ErrorFormat {
197 #[default]
199 Human,
200 Json,
202 RustcJson,
204 }
205}
206
207#[derive(Clone)]
209pub struct ImportRemapping {
210 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#[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 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}