1use crate::{
4 CompilerOutput, CompilerStage, Dump, ErrorFormat, EvmVersion, ImportRemapping, Language,
5 Threads,
6};
7use std::{num::NonZeroUsize, path::PathBuf};
8
9#[cfg(feature = "clap")]
10use clap::{ColorChoice, 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(feature = "clap")] #[cfg_attr(
106 feature = "clap",
107 arg(help_heading = "Display options", long, value_enum, default_value = "auto")
108 )]
109 pub color: ColorChoice,
110 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long, short))]
112 pub verbose: bool,
113 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
117 pub pretty_json: bool,
118 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
120 pub pretty_json_err: bool,
121 #[cfg_attr(
123 feature = "clap",
124 arg(help_heading = "Display options", long, value_enum, default_value_t)
125 )]
126 pub error_format: ErrorFormat,
127 #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
129 pub no_warnings: bool,
130
131 #[doc(hidden)]
135 #[cfg_attr(feature = "clap", arg(id = "unstable-features", value_name = "FLAG", short = 'Z'))]
136 pub _unstable: Vec<String>,
137
138 #[cfg_attr(feature = "clap", arg(skip))]
140 pub unstable: UnstableOpts,
141
142 #[doc(hidden)]
144 #[cfg_attr(feature = "clap", arg(skip))]
145 pub _non_exhaustive: (),
146}
147
148impl Opts {
149 #[inline]
151 pub fn threads(&self) -> NonZeroUsize {
152 self.threads.0
153 }
154
155 #[cfg(feature = "clap")]
157 pub fn finish(&mut self) -> Result<(), clap::Error> {
158 self.import_remappings = self
159 .input
160 .iter()
161 .filter(|s| s.contains('='))
162 .map(|s| {
163 s.parse::<ImportRemapping>().map_err(|e| {
164 make_clap_error(
165 clap::error::ErrorKind::InvalidValue,
166 format!("invalid remapping {s:?}: {e}"),
167 )
168 })
169 })
170 .collect::<Result<_, _>>()?;
171 self.input.retain(|s| !s.contains('='));
172
173 if !self._unstable.is_empty() {
174 let hack = self._unstable.iter().map(|s| format!("--{s}"));
175 let args = std::iter::once(String::new()).chain(hack);
176 self.unstable = UnstableOpts::try_parse_from(args).map_err(|e| {
177 override_clap_message(e, |s| {
178 s.replace("solar-config", "solar").replace("error:", "").replace("--", "-Z")
179 })
180 })?;
181 }
182
183 Ok(())
184 }
185}
186
187#[cfg(feature = "clap")]
189fn override_clap_message(e: clap::Error, f: impl FnOnce(String) -> String) -> clap::Error {
190 let msg = f(e.render().ansi().to_string());
191 let msg = msg.trim();
192 make_clap_error(e.kind(), msg)
193}
194
195#[cfg(feature = "clap")]
196fn make_clap_error(kind: clap::error::ErrorKind, message: impl std::fmt::Display) -> clap::Error {
197 <Opts as clap::CommandFactory>::command().error(kind, message)
198}
199
200#[derive(Clone, Debug, Default)]
202#[cfg_attr(feature = "clap", derive(Parser))]
203#[cfg_attr(feature = "clap", clap(
204 disable_help_flag = true,
205 before_help = concat!(
206 "List of all unstable flags.\n",
207 "WARNING: these are completely unstable, and may change at any time!\n",
208 " NOTE: the following flags should be passed on the command-line using `-Z`, not `--`",
210 ),
211 help_template = "{before-help}{all-args}"
212))]
213#[allow(clippy::manual_non_exhaustive)]
214pub struct UnstableOpts {
215 #[cfg_attr(feature = "clap", arg(long))]
217 pub ui_testing: bool,
218
219 #[cfg_attr(feature = "clap", arg(long))]
223 pub track_diagnostics: bool,
224
225 #[cfg_attr(feature = "clap", arg(long))]
227 pub parse_yul: bool,
228
229 #[cfg_attr(feature = "clap", arg(long))]
231 pub no_resolve_imports: bool,
232
233 #[cfg_attr(feature = "clap", arg(long, value_name = "KIND[=PATHS...]"))]
237 pub dump: Option<Dump>,
238
239 #[cfg_attr(feature = "clap", arg(long))]
241 pub ast_stats: bool,
242
243 #[cfg_attr(feature = "clap", arg(long))]
245 pub span_visitor: bool,
246
247 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Help))]
249 pub help: (),
250
251 #[doc(hidden)]
253 #[cfg_attr(feature = "clap", arg(skip))]
254 pub _non_exhaustive: (),
255
256 #[cfg(test)]
257 #[cfg_attr(feature = "clap", arg(long))]
258 pub test_bool: bool,
259 #[cfg(test)]
260 #[cfg_attr(feature = "clap", arg(long))]
261 pub test_value: Option<usize>,
262}
263
264#[cfg(all(test, feature = "clap"))]
265mod tests {
266 use super::*;
267 use clap::CommandFactory;
268
269 #[test]
270 fn verify_cli() {
271 Opts::command().debug_assert();
272 let _ = Opts::default();
273 let _ = Opts { evm_version: EvmVersion::Berlin, ..Default::default() };
274
275 UnstableOpts::command().debug_assert();
276 let _ = UnstableOpts::default();
277 let _ = UnstableOpts { ast_stats: false, ..Default::default() };
278 }
279
280 #[test]
281 fn unstable_features() {
282 fn parse(args: &[&str]) -> Result<UnstableOpts, impl std::fmt::Debug> {
283 struct UnwrapDisplay<T>(T);
284 impl<T: std::fmt::Display> std::fmt::Debug for UnwrapDisplay<T> {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 write!(f, "\n{}", self.0)
287 }
288 }
289 (|| {
290 let mut opts = Opts::try_parse_from(args)?;
291 opts.finish()?;
292 Ok::<_, clap::Error>(opts.unstable)
293 })()
294 .map_err(|e| UnwrapDisplay(e.render().ansi().to_string()))
295 }
296
297 let unstable = parse(&["solar", "a.sol"]).unwrap();
298 assert!(!unstable.test_bool);
299
300 let unstable = parse(&["solar", "-Ztest-bool", "a.sol"]).unwrap();
301 assert!(unstable.test_bool);
302 let unstable = parse(&["solar", "-Z", "test-bool", "a.sol"]).unwrap();
303 assert!(unstable.test_bool);
304
305 assert!(parse(&["solar", "-Ztest-value", "a.sol"]).is_err());
306 assert!(parse(&["solar", "-Z", "test-value", "a.sol"]).is_err());
307 assert!(parse(&["solar", "-Ztest-value", "2", "a.sol"]).is_err());
308 let unstable = parse(&["solar", "-Ztest-value=2", "a.sol"]).unwrap();
309 assert_eq!(unstable.test_value, Some(2));
310 let unstable = parse(&["solar", "-Z", "test-value=2", "a.sol"]).unwrap();
311 assert_eq!(unstable.test_value, Some(2));
312
313 let unstable = parse(&["solar", "-Zast-stats", "a.sol"]).unwrap();
314 assert!(unstable.ast_stats);
315 }
316}