use crate::{
ColorChoice, CompilerOutput, CompilerStage, Dump, ErrorFormat, EvmVersion, HumanEmitterKind,
ImportRemapping, Language, OptimizationMode, Threads,
};
use std::{num::NonZeroUsize, path::PathBuf};
#[cfg(feature = "clap")]
use clap::{Parser, ValueHint};
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "clap", derive(Parser))]
#[cfg_attr(feature = "clap", command(
name = "solar",
version = crate::version::short_version(),
long_version = crate::version::version(),
arg_required_else_help = true,
))]
#[allow(clippy::manual_non_exhaustive)]
pub struct CompileOpts {
#[cfg_attr(feature = "clap", arg(value_hint = ValueHint::FilePath))]
pub input: Vec<String>,
#[cfg_attr(feature = "clap", arg(skip))]
pub import_remappings: Vec<ImportRemapping>,
#[cfg_attr(
feature = "clap",
arg(
help_heading = "Input options",
long,
value_hint = ValueHint::DirPath,
)
)]
pub base_path: Option<PathBuf>,
#[cfg_attr(
feature = "clap",
arg(
help_heading = "Input options",
name = "include-path",
value_name = "INCLUDE_PATH",
long,
short = 'I',
alias = "import-path",
value_hint = ValueHint::DirPath,
)
)]
pub include_paths: Vec<PathBuf>,
#[cfg_attr(
feature = "clap",
arg(
help_heading = "Input options",
long,
value_delimiter = ',',
value_hint = ValueHint::DirPath,
)
)]
pub allow_paths: Vec<PathBuf>,
#[cfg_attr(
feature = "clap",
arg(help_heading = "Input options", long, value_enum, default_value_t, hide = true)
)]
pub language: Language,
#[cfg_attr(feature = "clap", arg(long, short = 'j', visible_alias = "jobs", default_value_t))]
pub threads: Threads,
#[cfg_attr(feature = "clap", arg(long, value_enum, default_value_t))]
pub evm_version: EvmVersion,
#[cfg_attr(feature = "clap", arg(long, value_enum))]
pub stop_after: Option<CompilerStage>,
#[cfg_attr(feature = "clap", arg(short = 'O', long = "optimize", value_enum, default_value_t))]
pub optimization: OptimizationMode,
#[cfg_attr(feature = "clap", arg(long, value_hint = ValueHint::DirPath))]
pub out_dir: Option<PathBuf>,
#[cfg_attr(feature = "clap", arg(long, value_delimiter = ','))]
pub emit: Vec<CompilerOutput>,
#[cfg_attr(feature = "clap", arg(long))]
pub standard_json: bool,
#[cfg_attr(
feature = "clap",
arg(help_heading = "Display options", long, value_parser = ColorChoiceValueParser::default(), default_value = "auto")
)]
pub color: ColorChoice,
#[cfg_attr(feature = "clap", arg(help_heading = "Display options", long, short))]
pub verbose: bool,
#[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
pub pretty_json: bool,
#[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
pub pretty_json_err: bool,
#[cfg_attr(
feature = "clap",
arg(help_heading = "Display options", long, value_enum, default_value_t)
)]
pub error_format: ErrorFormat,
#[cfg_attr(
feature = "clap",
arg(
help_heading = "Display options",
long,
value_name = "VALUE",
value_enum,
default_value_t
)
)]
pub error_format_human: HumanEmitterKind,
#[cfg_attr(
feature = "clap",
arg(help_heading = "Display options", long, value_name = "WIDTH")
)]
pub diagnostic_width: Option<usize>,
#[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
pub no_warnings: bool,
#[cfg_attr(
feature = "clap",
arg(help_heading = "Display options", long, value_name = "CODE", value_delimiter = ',')
)]
pub allow: Vec<String>,
#[doc(hidden)]
#[cfg_attr(feature = "clap", arg(id = "unstable-features", value_name = "FLAG", short = 'Z'))]
pub _unstable: Vec<String>,
#[cfg_attr(feature = "clap", arg(skip))]
pub unstable: UnstableOpts,
#[doc(hidden)]
#[cfg_attr(feature = "clap", arg(skip))]
pub _non_exhaustive: (),
}
impl CompileOpts {
#[inline]
pub const fn optimize_mir(&self) -> bool {
!matches!(self.optimization, OptimizationMode::None)
}
#[inline]
pub fn threads(&self) -> NonZeroUsize {
self.threads.0
}
#[cfg(feature = "clap")]
pub fn finish(&mut self) -> Result<(), clap::Error> {
if self.standard_json {
if self.input.iter().any(|s| s.contains('=')) {
return Err(make_clap_error(
clap::error::ErrorKind::InvalidValue,
"Import remappings are not accepted on the command line in Standard JSON mode.\n\
Please put them under 'settings.remappings' in the JSON input.",
));
}
if self.input.len() > 1 {
return Err(make_clap_error(
clap::error::ErrorKind::TooManyValues,
"Too many input files for --standard-json.\n\
Please either specify a single file name or provide its content on standard input.",
));
}
}
self.import_remappings = self
.input
.iter()
.filter(|s| s.contains('='))
.map(|s| {
s.parse::<ImportRemapping>().map_err(|e| {
make_clap_error(
clap::error::ErrorKind::InvalidValue,
format!("invalid remapping {s:?}: {e}"),
)
})
})
.collect::<Result<_, _>>()?;
self.input.retain(|s| !s.contains('='));
if !self._unstable.is_empty() {
let hack = self._unstable.iter().map(|s| format!("--{s}"));
let args = std::iter::once(String::new()).chain(hack);
self.unstable = UnstableOpts::try_parse_from(args).map_err(|e| {
override_clap_message(e, |s| {
s.replace("solar-config", "solar").replace("error:", "").replace("--", "-Z")
})
})?;
}
Ok(())
}
}
#[cfg(feature = "clap")]
fn override_clap_message(e: clap::Error, f: impl FnOnce(String) -> String) -> clap::Error {
let msg = f(e.render().ansi().to_string());
let msg = msg.trim();
make_clap_error(e.kind(), msg)
}
#[cfg(feature = "clap")]
fn make_clap_error(kind: clap::error::ErrorKind, message: impl std::fmt::Display) -> clap::Error {
<CompileOpts as clap::CommandFactory>::command().error(kind, message)
}
#[cfg(feature = "clap")]
#[derive(Clone, Default)]
struct ColorChoiceValueParser(clap::builder::EnumValueParser<clap::ColorChoice>);
#[cfg(feature = "clap")]
impl clap::builder::TypedValueParser for ColorChoiceValueParser {
type Value = ColorChoice;
fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
self.0.parse_ref(cmd, arg, value).map(map_color_choice)
}
fn possible_values(
&self,
) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
self.0.possible_values()
}
}
#[cfg(feature = "clap")]
fn map_color_choice(c: clap::ColorChoice) -> ColorChoice {
match c {
clap::ColorChoice::Auto => ColorChoice::Auto,
clap::ColorChoice::Always => ColorChoice::Always,
clap::ColorChoice::Never => ColorChoice::Never,
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "clap", derive(Parser))]
#[cfg_attr(feature = "clap", clap(
disable_help_flag = true,
before_help = concat!(
"List of all unstable flags.\n",
"WARNING: these are completely unstable, and may change at any time!",
),
help_template = "{before-help}{all-args}"
))]
#[allow(clippy::manual_non_exhaustive)]
pub struct UnstableOpts {
#[cfg_attr(feature = "clap", arg(long))]
pub ui_testing: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub track_diagnostics: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub parse_yul: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub no_resolve_imports: bool,
#[cfg_attr(feature = "clap", arg(long, require_equals = true, value_name = "KIND[=PATHS...]"))]
pub dump: Option<Dump>,
#[cfg_attr(feature = "clap", arg(long))]
pub ast_stats: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub hir_stats: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub standard_json_stats: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub span_visitor: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub print_max_storage_sizes: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub print_natspec: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub typeck: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub mir_print_after_each: bool,
#[cfg_attr(feature = "clap", arg(long))]
pub codegen: bool,
#[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Help))]
pub help: (),
#[doc(hidden)]
#[cfg_attr(feature = "clap", arg(skip))]
pub _non_exhaustive: (),
#[cfg(test)]
#[cfg_attr(feature = "clap", arg(long))]
pub test_bool: bool,
#[cfg(test)]
#[cfg_attr(feature = "clap", arg(long))]
pub test_value: Option<usize>,
}
#[cfg(all(test, feature = "clap"))]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
CompileOpts::command().debug_assert();
let _ = CompileOpts::default();
let _ = CompileOpts { evm_version: EvmVersion::Berlin, ..Default::default() };
UnstableOpts::command().debug_assert();
let _ = UnstableOpts::default();
let _ = UnstableOpts { ast_stats: false, ..Default::default() };
}
#[test]
fn allow() {
let mut opts =
CompileOpts::try_parse_from(["solar", "--allow", "1234,5678", "a.sol"]).unwrap();
opts.finish().unwrap();
assert_eq!(opts.allow, ["1234", "5678"]);
}
#[test]
fn standard_json_input() {
let mut opts = CompileOpts::try_parse_from(["solar", "--standard-json"]).unwrap();
opts.finish().unwrap();
assert!(opts.input.is_empty());
let mut opts = CompileOpts::try_parse_from(["solar", "--standard-json", "-"]).unwrap();
opts.finish().unwrap();
assert_eq!(opts.input, ["-"]);
let mut opts =
CompileOpts::try_parse_from(["solar", "--standard-json", "input.json"]).unwrap();
opts.finish().unwrap();
assert_eq!(opts.input, ["input.json"]);
}
#[test]
fn standard_json_rejects_multiple_inputs() {
let mut opts =
CompileOpts::try_parse_from(["solar", "--standard-json", "input1.json", "input2.json"])
.unwrap();
let error = opts.finish().unwrap_err().render().ansi().to_string();
assert!(error.contains("Too many input files for --standard-json."));
}
#[test]
fn standard_json_rejects_remappings() {
let mut opts = CompileOpts::try_parse_from(["solar", "--standard-json", "a=b"]).unwrap();
let error = opts.finish().unwrap_err().render().ansi().to_string();
assert!(error.contains("Import remappings are not accepted on the command line"));
}
#[test]
fn unstable_features() {
fn parse(args: &[&str]) -> Result<UnstableOpts, impl std::fmt::Debug> {
struct UnwrapDisplay<T>(T);
impl<T: std::fmt::Display> std::fmt::Debug for UnwrapDisplay<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\n{}", self.0)
}
}
(|| {
let mut opts = CompileOpts::try_parse_from(args)?;
opts.finish()?;
Ok::<_, clap::Error>(opts.unstable)
})()
.map_err(|e| UnwrapDisplay(e.render().ansi().to_string()))
}
let unstable = parse(&["solar", "a.sol"]).unwrap();
assert!(!unstable.test_bool);
let unstable = parse(&["solar", "-Ztest-bool", "a.sol"]).unwrap();
assert!(unstable.test_bool);
let unstable = parse(&["solar", "-Z", "test-bool", "a.sol"]).unwrap();
assert!(unstable.test_bool);
assert!(parse(&["solar", "-Ztest-value", "a.sol"]).is_err());
assert!(parse(&["solar", "-Z", "test-value", "a.sol"]).is_err());
assert!(parse(&["solar", "-Ztest-value", "2", "a.sol"]).is_err());
let unstable = parse(&["solar", "-Ztest-value=2", "a.sol"]).unwrap();
assert_eq!(unstable.test_value, Some(2));
let unstable = parse(&["solar", "-Z", "test-value=2", "a.sol"]).unwrap();
assert_eq!(unstable.test_value, Some(2));
}
}