solar_config/
opts.rs

1//! Solar CLI arguments.
2
3use 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// 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(feature = "clap")] // TODO
105    #[cfg_attr(
106        feature = "clap",
107        arg(help_heading = "Display options", long, value_enum, default_value = "auto")
108    )]
109    pub color: ColorChoice,
110    /// Use verbose output.
111    #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long, short))]
112    pub verbose: bool,
113    /// Pretty-print JSON output.
114    ///
115    /// Does not include errors. See `--pretty-json-err`.
116    #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
117    pub pretty_json: bool,
118    /// Pretty-print error JSON output.
119    #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
120    pub pretty_json_err: bool,
121    /// How errors and other messages are produced.
122    #[cfg_attr(
123        feature = "clap",
124        arg(help_heading = "Display options", long, value_enum, default_value_t)
125    )]
126    pub error_format: ErrorFormat,
127    /// Whether to disable warnings.
128    #[cfg_attr(feature = "clap", arg(help_heading = "Display options", long))]
129    pub no_warnings: bool,
130
131    /// Unstable flags. WARNING: these are completely unstable, and may change at any time.
132    ///
133    /// See `-Zhelp` for more details.
134    #[doc(hidden)]
135    #[cfg_attr(feature = "clap", arg(id = "unstable-features", value_name = "FLAG", short = 'Z'))]
136    pub _unstable: Vec<String>,
137
138    /// Parsed unstable flags.
139    #[cfg_attr(feature = "clap", arg(skip))]
140    pub unstable: UnstableOpts,
141
142    // Allows `Opts { x: y, ..Default::default() }`.
143    #[doc(hidden)]
144    #[cfg_attr(feature = "clap", arg(skip))]
145    pub _non_exhaustive: (),
146}
147
148impl Opts {
149    /// Returns the number of threads to use.
150    #[inline]
151    pub fn threads(&self) -> NonZeroUsize {
152        self.threads.0
153    }
154
155    /// Finishes argument parsing.
156    #[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// Ideally would be clap::Error::raw but it never prints styled text.
188#[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/// Internal options.
201#[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        // TODO: This is pretty hard to fix, as we don't have access to the `Command` in the derive macros.
209        "   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    /// Enables UI testing mode.
216    #[cfg_attr(feature = "clap", arg(long))]
217    pub ui_testing: bool,
218
219    /// Prints a note for every diagnostic that is emitted with the creation and emission location.
220    ///
221    /// This is enabled by default on debug builds.
222    #[cfg_attr(feature = "clap", arg(long))]
223    pub track_diagnostics: bool,
224
225    /// Enables parsing Yul files for testing.
226    #[cfg_attr(feature = "clap", arg(long))]
227    pub parse_yul: bool,
228
229    /// Disables import resolution.
230    #[cfg_attr(feature = "clap", arg(long))]
231    pub no_resolve_imports: bool,
232
233    /// Print additional information about the compiler's internal state.
234    ///
235    /// Valid kinds are `ast` and `hir`.
236    #[cfg_attr(feature = "clap", arg(long, value_name = "KIND[=PATHS...]"))]
237    pub dump: Option<Dump>,
238
239    /// Print AST stats.
240    #[cfg_attr(feature = "clap", arg(long))]
241    pub ast_stats: bool,
242
243    /// Run the span visitor after parsing.
244    #[cfg_attr(feature = "clap", arg(long))]
245    pub span_visitor: bool,
246
247    /// Print help.
248    #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Help))]
249    pub help: (),
250
251    // Allows `UnstableOpts { x: y, ..Default::default() }`.
252    #[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}