1use crate::{
4 ColorChoice, CompilerOutput, CompilerStage, Dump, ErrorFormat, EvmVersion, HumanEmitterKind,
5 ImportRemapping, Language, Threads,
6};
7use std::{num::NonZeroUsize, path::PathBuf};
8
9#[cfg(feature = "clap")]
10use clap::{Parser, ValueHint};
11
12#[derive(Clone, Debug, Default)]
16#[cfg_attr(feature = "clap", derive(Parser))]
17#[cfg_attr(feature = "clap", command(
18 name = "solar",
19 version = crate::version::SHORT_VERSION,
20 long_version = crate::version::LONG_VERSION,
21 arg_required_else_help = true,
22))]
23#[allow(clippy::manual_non_exhaustive)]
24pub struct Opts {
25 #[cfg_attr(feature = "clap", arg(value_hint = ValueHint::FilePath))]
33 pub input: Vec<String>,
34 #[cfg_attr(feature = "clap", arg(skip))]
41 pub import_remappings: Vec<ImportRemapping>,
42 #[cfg_attr(
44 feature = "clap",
45 arg(
46 help_heading = "Input options",
47 long,
48 value_hint = ValueHint::DirPath,
49 )
50 )]
51 pub base_path: Option<PathBuf>,
52 #[cfg_attr(
56 feature = "clap",
57 arg(
58 help_heading = "Input options",
59 name = "include-path",
60 value_name = "INCLUDE_PATH",
61 long,
62 short = 'I',
63 alias = "import-path",
64 value_hint = ValueHint::DirPath,
65 )
66 )]
67 pub include_paths: Vec<PathBuf>,
68 #[cfg_attr(
70 feature = "clap",
71 arg(
72 help_heading = "Input options",
73 long,
74 value_delimiter = ',',
75 value_hint = ValueHint::DirPath,
76 )
77 )]
78 pub allow_paths: Vec<PathBuf>,
79 #[cfg_attr(
81 feature = "clap",
82 arg(help_heading = "Input options", long, value_enum, default_value_t, hide = true)
83 )]
84 pub language: Language,
85
86 #[cfg_attr(feature = "clap", arg(long, short = 'j', visible_alias = "jobs", default_value_t))]
88 pub threads: Threads,
89 #[cfg_attr(feature = "clap", arg(long, value_enum, default_value_t))]
91 pub evm_version: EvmVersion,
92 #[cfg_attr(feature = "clap", arg(long, value_enum))]
94 pub stop_after: Option<CompilerStage>,
95
96 #[cfg_attr(feature = "clap", arg(long, value_hint = ValueHint::DirPath))]
98 pub out_dir: Option<PathBuf>,
99 #[cfg_attr(feature = "clap", arg(long, value_delimiter = ','))]
101 pub emit: Vec<CompilerOutput>,
102
103 #[cfg_attr(
105 feature = "clap",
106 arg(help_heading = "Display options", long, value_parser = ColorChoiceValueParser::default(), default_value = "auto")
107 )]
108 pub color: ColorChoice,
109 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long, short))]
111 pub verbose: bool,
112 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
116 pub pretty_json: bool,
117 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
119 pub pretty_json_err: bool,
120 #[cfg_attr(
122 feature = "clap",
123 arg(help_heading = "Display options", long, value_enum, default_value_t)
124 )]
125 pub error_format: ErrorFormat,
126 #[cfg_attr(
128 feature = "clap",
129 arg(
130 help_heading = "Display options",
131 long,
132 value_name = "VALUE",
133 value_enum,
134 default_value_t
135 )
136 )]
137 pub error_format_human: HumanEmitterKind,
138 #[cfg_attr(
140 feature = "clap",
141 arg(help_heading = "Display options", long, value_name = "WIDTH")
142 )]
143 pub diagnostic_width: Option<usize>,
144 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
146 pub no_warnings: bool,
147
148 #[doc(hidden)]
152 #[cfg_attr(feature = "clap", arg(id = "unstable-features", value_name = "FLAG", short = 'Z'))]
153 pub _unstable: Vec<String>,
154
155 #[cfg_attr(feature = "clap", arg(skip))]
157 pub unstable: UnstableOpts,
158
159 #[doc(hidden)]
161 #[cfg_attr(feature = "clap", arg(skip))]
162 pub _non_exhaustive: (),
163}
164
165impl Opts {
166 #[inline]
168 pub fn threads(&self) -> NonZeroUsize {
169 self.threads.0
170 }
171
172 #[cfg(feature = "clap")]
174 pub fn finish(&mut self) -> Result<(), clap::Error> {
175 self.import_remappings = self
176 .input
177 .iter()
178 .filter(|s| s.contains('='))
179 .map(|s| {
180 s.parse::<ImportRemapping>().map_err(|e| {
181 make_clap_error(
182 clap::error::ErrorKind::InvalidValue,
183 format!("invalid remapping {s:?}: {e}"),
184 )
185 })
186 })
187 .collect::<Result<_, _>>()?;
188 self.input.retain(|s| !s.contains('='));
189
190 if !self._unstable.is_empty() {
191 let hack = self._unstable.iter().map(|s| format!("--{s}"));
192 let args = std::iter::once(String::new()).chain(hack);
193 self.unstable = UnstableOpts::try_parse_from(args).map_err(|e| {
194 override_clap_message(e, |s| {
195 s.replace("solar-config", "solar").replace("error:", "").replace("--", "-Z")
196 })
197 })?;
198 }
199
200 Ok(())
201 }
202}
203
204#[cfg(feature = "clap")]
206fn override_clap_message(e: clap::Error, f: impl FnOnce(String) -> String) -> clap::Error {
207 let msg = f(e.render().ansi().to_string());
208 let msg = msg.trim();
209 make_clap_error(e.kind(), msg)
210}
211
212#[cfg(feature = "clap")]
213fn make_clap_error(kind: clap::error::ErrorKind, message: impl std::fmt::Display) -> clap::Error {
214 <Opts as clap::CommandFactory>::command().error(kind, message)
215}
216
217#[cfg(feature = "clap")]
218#[derive(Clone, Default)]
219struct ColorChoiceValueParser(clap::builder::EnumValueParser<clap::ColorChoice>);
220
221#[cfg(feature = "clap")]
222impl clap::builder::TypedValueParser for ColorChoiceValueParser {
223 type Value = ColorChoice;
224
225 fn parse_ref(
226 &self,
227 cmd: &clap::Command,
228 arg: Option<&clap::Arg>,
229 value: &std::ffi::OsStr,
230 ) -> Result<Self::Value, clap::Error> {
231 self.0.parse_ref(cmd, arg, value).map(map_color_choice)
232 }
233
234 fn possible_values(
235 &self,
236 ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
237 self.0.possible_values()
238 }
239}
240
241#[cfg(feature = "clap")]
242fn map_color_choice(c: clap::ColorChoice) -> ColorChoice {
243 match c {
244 clap::ColorChoice::Auto => ColorChoice::Auto,
245 clap::ColorChoice::Always => ColorChoice::Always,
246 clap::ColorChoice::Never => ColorChoice::Never,
247 }
248}
249
250#[derive(Clone, Debug, Default)]
252#[cfg_attr(feature = "clap", derive(Parser))]
253#[cfg_attr(feature = "clap", clap(
254 disable_help_flag = true,
255 before_help = concat!(
256 "List of all unstable flags.\n",
257 "WARNING: these are completely unstable, and may change at any time!",
258 ),
259 help_template = "{before-help}{all-args}"
260))]
261#[allow(clippy::manual_non_exhaustive)]
262pub struct UnstableOpts {
263 #[cfg_attr(feature = "clap", arg(long))]
265 pub ui_testing: bool,
266
267 #[cfg_attr(feature = "clap", arg(long))]
271 pub track_diagnostics: bool,
272
273 #[cfg_attr(feature = "clap", arg(long))]
275 pub parse_yul: bool,
276
277 #[cfg_attr(feature = "clap", arg(long))]
279 pub no_resolve_imports: bool,
280
281 #[cfg_attr(feature = "clap", arg(long, require_equals = true, value_name = "KIND[=PATHS...]"))]
285 pub dump: Option<Dump>,
286
287 #[cfg_attr(feature = "clap", arg(long))]
289 pub ast_stats: bool,
290
291 #[cfg_attr(feature = "clap", arg(long))]
293 pub span_visitor: bool,
294
295 #[cfg_attr(feature = "clap", arg(long))]
297 pub print_max_storage_sizes: bool,
298
299 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Help))]
301 pub help: (),
302
303 #[doc(hidden)]
305 #[cfg_attr(feature = "clap", arg(skip))]
306 pub _non_exhaustive: (),
307
308 #[cfg(test)]
309 #[cfg_attr(feature = "clap", arg(long))]
310 pub test_bool: bool,
311
312 #[cfg(test)]
313 #[cfg_attr(feature = "clap", arg(long))]
314 pub test_value: Option<usize>,
315}
316
317#[cfg(all(test, feature = "clap"))]
318mod tests {
319 use super::*;
320 use clap::CommandFactory;
321
322 #[test]
323 fn verify_cli() {
324 Opts::command().debug_assert();
325 let _ = Opts::default();
326 let _ = Opts { evm_version: EvmVersion::Berlin, ..Default::default() };
327
328 UnstableOpts::command().debug_assert();
329 let _ = UnstableOpts::default();
330 let _ = UnstableOpts { ast_stats: false, ..Default::default() };
331 }
332
333 #[test]
334 fn unstable_features() {
335 fn parse(args: &[&str]) -> Result<UnstableOpts, impl std::fmt::Debug> {
336 struct UnwrapDisplay<T>(T);
337 impl<T: std::fmt::Display> std::fmt::Debug for UnwrapDisplay<T> {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 write!(f, "\n{}", self.0)
340 }
341 }
342 (|| {
343 let mut opts = Opts::try_parse_from(args)?;
344 opts.finish()?;
345 Ok::<_, clap::Error>(opts.unstable)
346 })()
347 .map_err(|e| UnwrapDisplay(e.render().ansi().to_string()))
348 }
349
350 let unstable = parse(&["solar", "a.sol"]).unwrap();
351 assert!(!unstable.test_bool);
352
353 let unstable = parse(&["solar", "-Ztest-bool", "a.sol"]).unwrap();
354 assert!(unstable.test_bool);
355 let unstable = parse(&["solar", "-Z", "test-bool", "a.sol"]).unwrap();
356 assert!(unstable.test_bool);
357
358 assert!(parse(&["solar", "-Ztest-value", "a.sol"]).is_err());
359 assert!(parse(&["solar", "-Z", "test-value", "a.sol"]).is_err());
360 assert!(parse(&["solar", "-Ztest-value", "2", "a.sol"]).is_err());
361 let unstable = parse(&["solar", "-Ztest-value=2", "a.sol"]).unwrap();
362 assert_eq!(unstable.test_value, Some(2));
363 let unstable = parse(&["solar", "-Z", "test-value=2", "a.sol"]).unwrap();
364 assert_eq!(unstable.test_value, Some(2));
365
366 let unstable = parse(&["solar", "-Zast-stats", "a.sol"]).unwrap();
367 assert!(unstable.ast_stats);
368 }
369}