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
26pub const SINGLE_THREADED_TARGET: bool =
33 cfg!(target_os = "emscripten") || cfg!(target_family = "wasm") || cfg!(target_os = "zkvm");
34
35str_enum! {
36 #[derive(strum::EnumIs, strum::FromRepr)]
38 #[strum(serialize_all = "kebab-case")]
39 #[non_exhaustive]
40 pub enum CompilerStage {
41 Parsing,
45 Lowering,
49 Analysis,
53 }
54}
55
56impl CompilerStage {
57 pub fn next(self) -> Option<Self> {
59 Self::from_repr(self as usize + 1)
60 }
61
62 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 #[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 #[derive(Default)]
87 #[strum(serialize_all = "camelCase")]
88 #[non_exhaustive]
89 pub enum EvmVersion {
90 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 #[derive(Default)]
151 #[strum(serialize_all = "kebab-case")]
152 #[non_exhaustive]
153 pub enum OptimizationMode {
154 None,
156 #[default]
158 Gas,
159 Size,
161 }
162}
163
164str_enum! {
165 #[strum(serialize_all = "kebab-case")]
167 #[non_exhaustive]
168 pub enum CompilerOutput {
169 Abi,
171 Bin,
173 BinRuntime,
175 Hashes,
177 Mir,
179 }
180}
181
182impl CompilerOutput {
183 pub fn is_codegen(self) -> bool {
186 matches!(self, Self::Bin | Self::BinRuntime | Self::Mir)
187 }
188}
189
190#[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 #[derive(EnumIs)]
214 #[strum(serialize_all = "kebab-case")]
215 #[non_exhaustive]
216 pub enum DumpKind {
217 Ast,
219 Hir,
221 }
222}
223
224str_enum! {
225 #[derive(Default)]
227 #[strum(serialize_all = "kebab-case")]
228 #[non_exhaustive]
229 pub enum ErrorFormat {
230 #[default]
232 Human,
233 Json,
235 RustcJson,
237 }
238}
239
240str_enum! {
241 #[derive(Default)]
243 #[strum(serialize_all = "kebab-case")]
244 #[non_exhaustive]
245 pub enum HumanEmitterKind {
246 Ascii,
248 #[default]
250 Unicode,
251 Short,
253 }
254}
255
256#[derive(Clone)]
258pub struct ImportRemapping {
259 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#[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 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}