solar_config/
opts.rs

1//! Solar CLI arguments.
2
3use 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// TODO: implement `allow_paths`.
13
14/// Blazingly fast Solidity compiler.
15#[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    /// Files to compile, or import remappings.
26    ///
27    /// `-` specifies standard input.
28    ///
29    /// Import remappings are specified as `[context:]prefix=path`.
30    /// See <https://docs.soliditylang.org/en/latest/path-resolution.html#import-remapping>.
31    // NOTE: Remappings are parsed away into the `import_remappings` field. Use that instead.
32    #[cfg_attr(feature = "clap", arg(value_hint = ValueHint::FilePath))]
33    pub input: Vec<String>,
34    /// Import remappings.
35    ///
36    /// This is either added manually when constructing the session or parsed from `input` into
37    /// this field.
38    ///
39    /// See <https://docs.soliditylang.org/en/latest/path-resolution.html#import-remapping>.
40    #[cfg_attr(feature = "clap", arg(skip))]
41    pub import_remappings: Vec<ImportRemapping>,
42    /// Use the given path as the root of the source tree.
43    #[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    /// Directory to search for files.
53    ///
54    /// Can be used multiple times.
55    #[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    /// Allow a given path for imports.
69    #[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    /// Source code language. Only Solidity is currently implemented.
80    #[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    /// Number of threads to use. Zero specifies the number of logical cores.
87    #[cfg_attr(feature = "clap", arg(long, short = 'j', visible_alias = "jobs", default_value_t))]
88    pub threads: Threads,
89    /// EVM version.
90    #[cfg_attr(feature = "clap", arg(long, value_enum, default_value_t))]
91    pub evm_version: EvmVersion,
92    /// Stop execution after the given compiler stage.
93    #[cfg_attr(feature = "clap", arg(long, value_enum))]
94    pub stop_after: Option<CompilerStage>,
95
96    /// Directory to write output files.
97    #[cfg_attr(feature = "clap", arg(long, value_hint = ValueHint::DirPath))]
98    pub out_dir: Option<PathBuf>,
99    /// Comma separated list of types of output for the compiler to emit.
100    #[cfg_attr(feature = "clap", arg(long, value_delimiter = ','))]
101    pub emit: Vec<CompilerOutput>,
102
103    /// Coloring.
104    #[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    /// Use verbose output.
110    #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long, short))]
111    pub verbose: bool,
112    /// Pretty-print JSON output.
113    ///
114    /// Does not include errors. See `--pretty-json-err`.
115    #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
116    pub pretty_json: bool,
117    /// Pretty-print error JSON output.
118    #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
119    pub pretty_json_err: bool,
120    /// How errors and other messages are produced.
121    #[cfg_attr(
122        feature = "clap",
123        arg(help_heading = "Display options", long, value_enum, default_value_t)
124    )]
125    pub error_format: ErrorFormat,
126    /// Human-readable error message style.
127    #[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    /// Terminal width for error message formatting.
139    #[cfg_attr(
140        feature = "clap",
141        arg(help_heading = "Display options", long, value_name = "WIDTH")
142    )]
143    pub diagnostic_width: Option<usize>,
144    /// Whether to disable warnings.
145    #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
146    pub no_warnings: bool,
147
148    /// Unstable flags. WARNING: these are completely unstable, and may change at any time.
149    ///
150    /// See `-Zhelp` for more details.
151    #[doc(hidden)]
152    #[cfg_attr(feature = "clap", arg(id = "unstable-features", value_name = "FLAG", short = 'Z'))]
153    pub _unstable: Vec<String>,
154
155    /// Parsed unstable flags.
156    #[cfg_attr(feature = "clap", arg(skip))]
157    pub unstable: UnstableOpts,
158
159    // Allows `Opts { x: y, ..Default::default() }`.
160    #[doc(hidden)]
161    #[cfg_attr(feature = "clap", arg(skip))]
162    pub _non_exhaustive: (),
163}
164
165impl Opts {
166    /// Returns the number of threads to use.
167    #[inline]
168    pub fn threads(&self) -> NonZeroUsize {
169        self.threads.0
170    }
171
172    /// Finishes argument parsing.
173    #[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// Ideally would be clap::Error::raw but it never prints styled text.
205#[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/// Internal options.
251#[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    /// Enables UI testing mode.
264    #[cfg_attr(feature = "clap", arg(long))]
265    pub ui_testing: bool,
266
267    /// Prints a note for every diagnostic that is emitted with the creation and emission location.
268    ///
269    /// This is enabled by default on debug builds.
270    #[cfg_attr(feature = "clap", arg(long))]
271    pub track_diagnostics: bool,
272
273    /// Enables parsing Yul files for testing.
274    #[cfg_attr(feature = "clap", arg(long))]
275    pub parse_yul: bool,
276
277    /// Disables import resolution.
278    #[cfg_attr(feature = "clap", arg(long))]
279    pub no_resolve_imports: bool,
280
281    /// Print additional information about the compiler's internal state.
282    ///
283    /// Valid kinds are `ast` and `hir`.
284    #[cfg_attr(feature = "clap", arg(long, require_equals = true, value_name = "KIND[=PATHS...]"))]
285    pub dump: Option<Dump>,
286
287    /// Print AST stats.
288    #[cfg_attr(feature = "clap", arg(long))]
289    pub ast_stats: bool,
290
291    /// Run the span visitor after parsing.
292    #[cfg_attr(feature = "clap", arg(long))]
293    pub span_visitor: bool,
294
295    /// Print contracts' max storage sizes.
296    #[cfg_attr(feature = "clap", arg(long))]
297    pub print_max_storage_sizes: bool,
298
299    /// Print help.
300    #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Help))]
301    pub help: (),
302
303    // Allows `UnstableOpts { x: y, ..Default::default() }`.
304    #[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}