#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/solar/main/assets/logo.png",
html_favicon_url = "https://raw.githubusercontent.com/paradigmxyz/solar/main/assets/favicon.ico"
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::{fmt, num::NonZeroUsize, sync::OnceLock};
use strum::EnumIs;
#[macro_use]
mod macros;
mod opts;
pub use opts::{CompileOpts, UnstableOpts};
mod lsp;
pub use lsp::LspArgs;
mod utils;
pub mod version;
pub use colorchoice::ColorChoice;
pub const SINGLE_THREADED_TARGET: bool =
cfg!(target_os = "emscripten") || cfg!(target_family = "wasm") || cfg!(target_os = "zkvm");
str_enum! {
#[derive(strum::EnumIs, strum::FromRepr)]
#[strum(serialize_all = "kebab-case")]
#[non_exhaustive]
pub enum CompilerStage {
Parsing,
Lowering,
Analysis,
}
}
impl CompilerStage {
pub fn next(self) -> Option<Self> {
Self::from_repr(self as usize + 1)
}
pub fn next_opt(this: Option<Self>) -> Option<Self> {
Self::from_repr(this.map(|s| s as usize + 1).unwrap_or(0))
}
}
str_enum! {
#[derive(Default)]
#[derive(strum::EnumIs)]
#[strum(serialize_all = "lowercase")]
#[non_exhaustive]
pub enum Language {
#[default]
Solidity,
Yul,
}
}
str_enum! {
#[derive(Default)]
#[strum(serialize_all = "camelCase")]
#[non_exhaustive]
pub enum EvmVersion {
Homestead,
TangerineWhistle,
SpuriousDragon,
Byzantium,
Constantinople,
Petersburg,
Istanbul,
Berlin,
London,
Paris,
Shanghai,
Cancun,
#[default]
Prague,
Osaka,
}
}
impl EvmVersion {
pub fn supports_returndata(self) -> bool {
self >= Self::Byzantium
}
pub fn has_static_call(self) -> bool {
self >= Self::Byzantium
}
pub fn has_bitwise_shifting(self) -> bool {
self >= Self::Constantinople
}
pub fn has_create2(self) -> bool {
self >= Self::Constantinople
}
pub fn has_ext_code_hash(self) -> bool {
self >= Self::Constantinople
}
pub fn has_chain_id(self) -> bool {
self >= Self::Istanbul
}
pub fn has_self_balance(self) -> bool {
self >= Self::Istanbul
}
pub fn has_base_fee(self) -> bool {
self >= Self::London
}
pub fn has_blob_base_fee(self) -> bool {
self >= Self::Cancun
}
pub fn has_prev_randao(self) -> bool {
self >= Self::Paris
}
pub fn has_push0(self) -> bool {
self >= Self::Shanghai
}
pub fn has_mcopy(self) -> bool {
self >= Self::Cancun
}
}
str_enum! {
#[derive(Default)]
#[strum(serialize_all = "kebab-case")]
#[non_exhaustive]
pub enum OptimizationMode {
None,
#[default]
Gas,
Size,
}
}
str_enum! {
#[strum(serialize_all = "kebab-case")]
#[non_exhaustive]
pub enum CompilerOutput {
Abi,
Bin,
BinRuntime,
Hashes,
Mir,
}
}
impl CompilerOutput {
pub fn is_codegen(self) -> bool {
matches!(self, Self::Bin | Self::BinRuntime | Self::Mir)
}
}
#[derive(Clone, Debug)]
pub struct Dump {
pub kind: DumpKind,
pub paths: Option<Vec<String>>,
}
impl std::str::FromStr for Dump {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (kind, paths) = if let Some((kind, paths)) = s.split_once('=') {
let paths = paths.split(',').map(ToString::to_string).collect();
(kind, Some(paths))
} else {
(s, None)
};
Ok(Self { kind: kind.parse::<DumpKind>().map_err(|e| e.to_string())?, paths })
}
}
str_enum! {
#[derive(EnumIs)]
#[strum(serialize_all = "kebab-case")]
#[non_exhaustive]
pub enum DumpKind {
Ast,
Hir,
}
}
str_enum! {
#[derive(Default)]
#[strum(serialize_all = "kebab-case")]
#[non_exhaustive]
pub enum ErrorFormat {
#[default]
Human,
Json,
RustcJson,
}
}
str_enum! {
#[derive(Default)]
#[strum(serialize_all = "kebab-case")]
#[non_exhaustive]
pub enum HumanEmitterKind {
Ascii,
#[default]
Unicode,
Short,
}
}
#[derive(Clone)]
pub struct ImportRemapping {
pub context: String,
pub prefix: String,
pub path: String,
}
impl std::str::FromStr for ImportRemapping {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((prefix_, path)) = s.split_once('=') {
let (context, prefix) = prefix_.split_once(':').unzip();
let prefix = prefix.unwrap_or(prefix_);
if prefix.is_empty() {
return Err("empty prefix");
}
Ok(Self {
context: context.unwrap_or_default().into(),
prefix: prefix.into(),
path: path.into(),
})
} else {
Err("missing '='")
}
}
}
impl fmt::Display for ImportRemapping {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.context.is_empty() {
write!(f, "{}:", self.context)?;
}
write!(f, "{}={}", self.prefix, self.path)
}
}
impl fmt::Debug for ImportRemapping {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ImportRemapping({self})")
}
}
#[derive(Clone, Copy)]
pub struct Threads(pub NonZeroUsize);
impl From<Threads> for NonZeroUsize {
fn from(threads: Threads) -> Self {
threads.0
}
}
impl From<NonZeroUsize> for Threads {
fn from(n: NonZeroUsize) -> Self {
Self(n)
}
}
impl From<usize> for Threads {
fn from(n: usize) -> Self {
Self::resolve(n)
}
}
impl Default for Threads {
fn default() -> Self {
Self::resolve(if SINGLE_THREADED_TARGET { 1 } else { 8.min(get_threads().get()) })
}
}
impl std::str::FromStr for Threads {
type Err = <NonZeroUsize as std::str::FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<usize>().map(Self::resolve)
}
}
impl std::fmt::Display for Threads {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::fmt::Debug for Threads {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl Threads {
pub fn resolve(n: usize) -> Self {
Self(NonZeroUsize::new(n).unwrap_or_else(get_threads))
}
}
fn get_threads() -> NonZeroUsize {
static THREADS: OnceLock<NonZeroUsize> = OnceLock::new();
*THREADS.get_or_init(|| std::thread::available_parallelism().unwrap_or(NonZeroUsize::MIN))
}
#[cfg(test)]
mod tests {
use super::*;
use strum::IntoEnumIterator;
#[cfg(not(feature = "serde"))]
use serde_json as _;
#[test]
fn string_enum() {
for value in EvmVersion::iter() {
let s = value.to_str();
assert_eq!(value.to_string(), s);
assert_eq!(value, s.parse().unwrap());
#[cfg(feature = "serde")]
{
let json_s = format!("\"{value}\"");
assert_eq!(serde_json::to_string(&value).unwrap(), json_s);
assert_eq!(serde_json::from_str::<EvmVersion>(&json_s).unwrap(), value);
}
}
}
}