1use crate::{
4 ColorChoice, CompilerOutput, CompilerStage, Dump, ErrorFormat, EvmVersion, HumanEmitterKind,
5 ImportRemapping, Language, OptimizationMode, 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::version(),
21 arg_required_else_help = true,
22))]
23#[allow(clippy::manual_non_exhaustive)]
24pub struct CompileOpts {
25 #[cfg_attr(feature = "clap", arg(value_hint = ValueHint::FilePath))]
36 pub input: Vec<String>,
37 #[cfg_attr(feature = "clap", arg(skip))]
44 pub import_remappings: Vec<ImportRemapping>,
45 #[cfg_attr(
47 feature = "clap",
48 arg(
49 help_heading = "Input options",
50 long,
51 value_hint = ValueHint::DirPath,
52 )
53 )]
54 pub base_path: Option<PathBuf>,
55 #[cfg_attr(
59 feature = "clap",
60 arg(
61 help_heading = "Input options",
62 name = "include-path",
63 value_name = "INCLUDE_PATH",
64 long,
65 short = 'I',
66 alias = "import-path",
67 value_hint = ValueHint::DirPath,
68 )
69 )]
70 pub include_paths: Vec<PathBuf>,
71 #[cfg_attr(
73 feature = "clap",
74 arg(
75 help_heading = "Input options",
76 long,
77 value_delimiter = ',',
78 value_hint = ValueHint::DirPath,
79 )
80 )]
81 pub allow_paths: Vec<PathBuf>,
82 #[cfg_attr(
84 feature = "clap",
85 arg(help_heading = "Input options", long, value_enum, default_value_t, hide = true)
86 )]
87 pub language: Language,
88
89 #[cfg_attr(feature = "clap", arg(long, short = 'j', visible_alias = "jobs", default_value_t))]
91 pub threads: Threads,
92 #[cfg_attr(feature = "clap", arg(long, value_enum, default_value_t))]
94 pub evm_version: EvmVersion,
95 #[cfg_attr(feature = "clap", arg(long, value_enum))]
97 pub stop_after: Option<CompilerStage>,
98 #[cfg_attr(feature = "clap", arg(short = 'O', long = "optimize", value_enum, default_value_t))]
100 pub optimization: OptimizationMode,
101
102 #[cfg_attr(feature = "clap", arg(long, value_hint = ValueHint::DirPath))]
104 pub out_dir: Option<PathBuf>,
105 #[cfg_attr(feature = "clap", arg(long, value_delimiter = ','))]
107 pub emit: Vec<CompilerOutput>,
108
109 #[cfg_attr(feature = "clap", arg(long))]
111 pub standard_json: bool,
112
113 #[cfg_attr(
115 feature = "clap",
116 arg(help_heading = "Display options", long, value_parser = ColorChoiceValueParser::default(), default_value = "auto")
117 )]
118 pub color: ColorChoice,
119 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long, short))]
121 pub verbose: bool,
122 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
126 pub pretty_json: bool,
127 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
129 pub pretty_json_err: bool,
130 #[cfg_attr(
132 feature = "clap",
133 arg(help_heading = "Display options", long, value_enum, default_value_t)
134 )]
135 pub error_format: ErrorFormat,
136 #[cfg_attr(
138 feature = "clap",
139 arg(
140 help_heading = "Display options",
141 long,
142 value_name = "VALUE",
143 value_enum,
144 default_value_t
145 )
146 )]
147 pub error_format_human: HumanEmitterKind,
148 #[cfg_attr(
150 feature = "clap",
151 arg(help_heading = "Display options", long, value_name = "WIDTH")
152 )]
153 pub diagnostic_width: Option<usize>,
154 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
156 pub no_warnings: bool,
157 #[cfg_attr(
159 feature = "clap",
160 arg(help_heading = "Display options", long, value_name = "CODE", value_delimiter = ',')
161 )]
162 pub allow: Vec<String>,
163
164 #[doc(hidden)]
168 #[cfg_attr(feature = "clap", arg(id = "unstable-features", value_name = "FLAG", short = 'Z'))]
169 pub _unstable: Vec<String>,
170
171 #[cfg_attr(feature = "clap", arg(skip))]
173 pub unstable: UnstableOpts,
174
175 #[doc(hidden)]
177 #[cfg_attr(feature = "clap", arg(skip))]
178 pub _non_exhaustive: (),
179}
180
181impl CompileOpts {
182 #[inline]
184 pub const fn optimize_mir(&self) -> bool {
185 !matches!(self.optimization, OptimizationMode::None)
186 }
187
188 #[inline]
190 pub fn threads(&self) -> NonZeroUsize {
191 self.threads.0
192 }
193
194 #[cfg(feature = "clap")]
196 pub fn finish(&mut self) -> Result<(), clap::Error> {
197 if self.standard_json {
198 if self.input.iter().any(|s| s.contains('=')) {
199 return Err(make_clap_error(
200 clap::error::ErrorKind::InvalidValue,
201 "Import remappings are not accepted on the command line in Standard JSON mode.\n\
202 Please put them under 'settings.remappings' in the JSON input.",
203 ));
204 }
205 if self.input.len() > 1 {
206 return Err(make_clap_error(
207 clap::error::ErrorKind::TooManyValues,
208 "Too many input files for --standard-json.\n\
209 Please either specify a single file name or provide its content on standard input.",
210 ));
211 }
212 }
213
214 self.import_remappings = self
215 .input
216 .iter()
217 .filter(|s| s.contains('='))
218 .map(|s| {
219 s.parse::<ImportRemapping>().map_err(|e| {
220 make_clap_error(
221 clap::error::ErrorKind::InvalidValue,
222 format!("invalid remapping {s:?}: {e}"),
223 )
224 })
225 })
226 .collect::<Result<_, _>>()?;
227 self.input.retain(|s| !s.contains('='));
228
229 if !self._unstable.is_empty() {
230 let hack = self._unstable.iter().map(|s| format!("--{s}"));
231 let args = std::iter::once(String::new()).chain(hack);
232 self.unstable = UnstableOpts::try_parse_from(args).map_err(|e| {
233 override_clap_message(e, |s| {
234 s.replace("solar-config", "solar").replace("error:", "").replace("--", "-Z")
235 })
236 })?;
237 }
238
239 Ok(())
240 }
241}
242
243#[cfg(feature = "clap")]
245fn override_clap_message(e: clap::Error, f: impl FnOnce(String) -> String) -> clap::Error {
246 let msg = f(e.render().ansi().to_string());
247 let msg = msg.trim();
248 make_clap_error(e.kind(), msg)
249}
250
251#[cfg(feature = "clap")]
252fn make_clap_error(kind: clap::error::ErrorKind, message: impl std::fmt::Display) -> clap::Error {
253 <CompileOpts as clap::CommandFactory>::command().error(kind, message)
254}
255
256#[cfg(feature = "clap")]
257#[derive(Clone, Default)]
258struct ColorChoiceValueParser(clap::builder::EnumValueParser<clap::ColorChoice>);
259
260#[cfg(feature = "clap")]
261impl clap::builder::TypedValueParser for ColorChoiceValueParser {
262 type Value = ColorChoice;
263
264 fn parse_ref(
265 &self,
266 cmd: &clap::Command,
267 arg: Option<&clap::Arg>,
268 value: &std::ffi::OsStr,
269 ) -> Result<Self::Value, clap::Error> {
270 self.0.parse_ref(cmd, arg, value).map(map_color_choice)
271 }
272
273 fn possible_values(
274 &self,
275 ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
276 self.0.possible_values()
277 }
278}
279
280#[cfg(feature = "clap")]
281fn map_color_choice(c: clap::ColorChoice) -> ColorChoice {
282 match c {
283 clap::ColorChoice::Auto => ColorChoice::Auto,
284 clap::ColorChoice::Always => ColorChoice::Always,
285 clap::ColorChoice::Never => ColorChoice::Never,
286 }
287}
288
289#[derive(Clone, Debug, Default)]
291#[cfg_attr(feature = "clap", derive(Parser))]
292#[cfg_attr(feature = "clap", clap(
293 disable_help_flag = true,
294 before_help = concat!(
295 "List of all unstable flags.\n",
296 "WARNING: these are completely unstable, and may change at any time!",
297 ),
298 help_template = "{before-help}{all-args}"
299))]
300#[allow(clippy::manual_non_exhaustive)]
301pub struct UnstableOpts {
302 #[cfg_attr(feature = "clap", arg(long))]
304 pub ui_testing: bool,
305
306 #[cfg_attr(feature = "clap", arg(long))]
310 pub track_diagnostics: bool,
311
312 #[cfg_attr(feature = "clap", arg(long))]
314 pub parse_yul: bool,
315
316 #[cfg_attr(feature = "clap", arg(long))]
318 pub no_resolve_imports: bool,
319
320 #[cfg_attr(feature = "clap", arg(long, require_equals = true, value_name = "KIND[=PATHS...]"))]
324 pub dump: Option<Dump>,
325
326 #[cfg_attr(feature = "clap", arg(long))]
328 pub ast_stats: bool,
329
330 #[cfg_attr(feature = "clap", arg(long))]
332 pub hir_stats: bool,
333
334 #[cfg_attr(feature = "clap", arg(long))]
336 pub standard_json_stats: bool,
337
338 #[cfg_attr(feature = "clap", arg(long))]
340 pub span_visitor: bool,
341
342 #[cfg_attr(feature = "clap", arg(long))]
344 pub print_max_storage_sizes: bool,
345
346 #[cfg_attr(feature = "clap", arg(long))]
348 pub print_natspec: bool,
349
350 #[cfg_attr(feature = "clap", arg(long))]
352 pub typeck: bool,
353
354 #[cfg_attr(feature = "clap", arg(long))]
356 pub mir_print_after_each: bool,
357
358 #[cfg_attr(feature = "clap", arg(long))]
364 pub codegen: bool,
365
366 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Help))]
371 pub help: (),
372
373 #[doc(hidden)]
375 #[cfg_attr(feature = "clap", arg(skip))]
376 pub _non_exhaustive: (),
377
378 #[cfg(test)]
379 #[cfg_attr(feature = "clap", arg(long))]
380 pub test_bool: bool,
381
382 #[cfg(test)]
383 #[cfg_attr(feature = "clap", arg(long))]
384 pub test_value: Option<usize>,
385}
386
387#[cfg(all(test, feature = "clap"))]
388mod tests {
389 use super::*;
390 use clap::CommandFactory;
391
392 #[test]
393 fn verify_cli() {
394 CompileOpts::command().debug_assert();
395 let _ = CompileOpts::default();
396 let _ = CompileOpts { evm_version: EvmVersion::Berlin, ..Default::default() };
397
398 UnstableOpts::command().debug_assert();
399 let _ = UnstableOpts::default();
400 let _ = UnstableOpts { ast_stats: false, ..Default::default() };
401 }
402
403 #[test]
404 fn allow() {
405 let mut opts =
406 CompileOpts::try_parse_from(["solar", "--allow", "1234,5678", "a.sol"]).unwrap();
407 opts.finish().unwrap();
408
409 assert_eq!(opts.allow, ["1234", "5678"]);
410 }
411
412 #[test]
413 fn standard_json_input() {
414 let mut opts = CompileOpts::try_parse_from(["solar", "--standard-json"]).unwrap();
415 opts.finish().unwrap();
416 assert!(opts.input.is_empty());
417
418 let mut opts = CompileOpts::try_parse_from(["solar", "--standard-json", "-"]).unwrap();
419 opts.finish().unwrap();
420 assert_eq!(opts.input, ["-"]);
421
422 let mut opts =
423 CompileOpts::try_parse_from(["solar", "--standard-json", "input.json"]).unwrap();
424 opts.finish().unwrap();
425 assert_eq!(opts.input, ["input.json"]);
426 }
427
428 #[test]
429 fn standard_json_rejects_multiple_inputs() {
430 let mut opts =
431 CompileOpts::try_parse_from(["solar", "--standard-json", "input1.json", "input2.json"])
432 .unwrap();
433 let error = opts.finish().unwrap_err().render().ansi().to_string();
434 assert!(error.contains("Too many input files for --standard-json."));
435 }
436
437 #[test]
438 fn standard_json_rejects_remappings() {
439 let mut opts = CompileOpts::try_parse_from(["solar", "--standard-json", "a=b"]).unwrap();
440 let error = opts.finish().unwrap_err().render().ansi().to_string();
441 assert!(error.contains("Import remappings are not accepted on the command line"));
442 }
443
444 #[test]
445 fn unstable_features() {
446 fn parse(args: &[&str]) -> Result<UnstableOpts, impl std::fmt::Debug> {
447 struct UnwrapDisplay<T>(T);
448 impl<T: std::fmt::Display> std::fmt::Debug for UnwrapDisplay<T> {
449 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
450 write!(f, "\n{}", self.0)
451 }
452 }
453 (|| {
454 let mut opts = CompileOpts::try_parse_from(args)?;
455 opts.finish()?;
456 Ok::<_, clap::Error>(opts.unstable)
457 })()
458 .map_err(|e| UnwrapDisplay(e.render().ansi().to_string()))
459 }
460
461 let unstable = parse(&["solar", "a.sol"]).unwrap();
462 assert!(!unstable.test_bool);
463
464 let unstable = parse(&["solar", "-Ztest-bool", "a.sol"]).unwrap();
465 assert!(unstable.test_bool);
466 let unstable = parse(&["solar", "-Z", "test-bool", "a.sol"]).unwrap();
467 assert!(unstable.test_bool);
468
469 assert!(parse(&["solar", "-Ztest-value", "a.sol"]).is_err());
470 assert!(parse(&["solar", "-Z", "test-value", "a.sol"]).is_err());
471 assert!(parse(&["solar", "-Ztest-value", "2", "a.sol"]).is_err());
472 let unstable = parse(&["solar", "-Ztest-value=2", "a.sol"]).unwrap();
473 assert_eq!(unstable.test_value, Some(2));
474 let unstable = parse(&["solar", "-Z", "test-value=2", "a.sol"]).unwrap();
475 assert_eq!(unstable.test_value, Some(2));
476
477 }
479}