xref: /wasmtime-44.0.1/crates/cli-flags/src/opt.rs (revision 133a0ef4)
1 //! Support for parsing Wasmtime's `-O`, `-W`, etc "option groups"
2 //!
3 //! This builds up a clap-derive-like system where there's ideally a single
4 //! macro `wasmtime_option_group!` which is invoked per-option which enables
5 //! specifying options in a struct-like syntax where all other boilerplate about
6 //! option parsing is contained exclusively within this module.
7 
8 use crate::{KeyValuePair, WasiNnGraph};
9 use clap::builder::{StringValueParser, TypedValueParser, ValueParserFactory};
10 use clap::error::{Error, ErrorKind};
11 use serde::de::{self, Visitor};
12 use std::path::PathBuf;
13 use std::str::FromStr;
14 use std::time::Duration;
15 use std::{fmt, marker};
16 use wasmtime::{Result, bail};
17 
18 /// Characters which can be safely ignored while parsing numeric options to wasmtime
19 const IGNORED_NUMBER_CHARS: [char; 1] = ['_'];
20 
21 #[macro_export]
22 macro_rules! wasmtime_option_group {
23     (
24         $(#[$attr:meta])*
25         pub struct $opts:ident {
26             $(
27                 $(#[doc = $doc:tt])*
28                 $(#[doc($doc_attr:meta)])?
29                 $(#[serde($serde_attr:meta)])*
30                 pub $opt:ident: $container:ident<$payload:ty>,
31             )+
32 
33             $(
34                 #[prefixed = $prefix:tt]
35                 $(#[serde($serde_attr2:meta)])*
36                 $(#[doc = $prefixed_doc:tt])*
37                 $(#[doc($prefixed_doc_attr:meta)])?
38                 pub $prefixed:ident: Vec<(String, Option<String>)>,
39             )?
40         }
41         enum $option:ident {
42             ...
43         }
44     ) => {
45         #[derive(Default, Debug)]
46         $(#[$attr])*
47         pub struct $opts {
48             $(
49                 $(#[serde($serde_attr)])*
50                 $(#[doc($doc_attr)])?
51                 pub $opt: $container<$payload>,
52             )+
53             $(
54                 $(#[serde($serde_attr2)])*
55                 pub $prefixed: Vec<(String, Option<String>)>,
56             )?
57         }
58 
59         #[derive(Clone, PartialEq)]
60         #[expect(non_camel_case_types, reason = "macro-generated code")]
61         enum $option {
62             $(
63                 $opt($payload),
64             )+
65             $(
66                 $prefixed(String, Option<String>),
67             )?
68         }
69 
70         impl $crate::opt::WasmtimeOption for $option {
71             const OPTIONS: &'static [$crate::opt::OptionDesc<$option>] = &[
72                 $(
73                     $crate::opt::OptionDesc {
74                         name: $crate::opt::OptName::Name(stringify!($opt)),
75                         parse: |_, s| {
76                             Ok($option::$opt(
77                                 $crate::opt::WasmtimeOptionValue::parse(s)?
78                             ))
79                         },
80                         val_help: <$payload as $crate::opt::WasmtimeOptionValue>::VAL_HELP,
81                         docs: concat!($($doc, "\n",)*),
82                     },
83                  )+
84                 $(
85                     $crate::opt::OptionDesc {
86                         name: $crate::opt::OptName::Prefix($prefix),
87                         parse: |name, val| {
88                             Ok($option::$prefixed(
89                                 name.to_string(),
90                                 val.map(|v| v.to_string()),
91                             ))
92                         },
93                         val_help: "[=val]",
94                         docs: concat!($($prefixed_doc, "\n",)*),
95                     },
96                  )?
97             ];
98         }
99 
100         impl core::fmt::Display for $option {
101             fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102                 match self {
103                     $(
104                         $option::$opt(val) => {
105                             write!(f, "{}=", stringify!($opt).replace('_', "-"))?;
106                             $crate::opt::WasmtimeOptionValue::display(val, f)
107                         }
108                     )+
109                     $(
110                         $option::$prefixed(key, val) => {
111                             write!(f, "{}-{key}", stringify!($prefixed))?;
112                             if let Some(val) = val {
113                                 write!(f, "={val}")?;
114                             }
115                             Ok(())
116                         }
117                     )?
118                 }
119             }
120         }
121 
122         impl $opts {
123             fn configure_with(&mut self, opts: &[$crate::opt::CommaSeparated<$option>]) {
124                 for opt in opts.iter().flat_map(|o| o.0.iter()) {
125                     match opt {
126                         $(
127                             $option::$opt(val) => {
128                                 $crate::opt::OptionContainer::push(&mut self.$opt, val.clone());
129                             }
130                         )+
131                         $(
132                             $option::$prefixed(key, val) => self.$prefixed.push((key.clone(), val.clone())),
133                         )?
134                     }
135                 }
136             }
137 
138             fn to_options(&self) -> Vec<$option> {
139                 let mut ret = Vec::new();
140                 $(
141                     for item in $crate::opt::OptionContainer::get(&self.$opt) {
142                         ret.push($option::$opt(item.clone()));
143                     }
144                 )+
145                 $(
146                     for (key,val) in self.$prefixed.iter() {
147                         ret.push($option::$prefixed(key.clone(), val.clone()));
148                     }
149                 )?
150                 ret
151             }
152         }
153     };
154 }
155 
156 /// Parser registered with clap which handles parsing the `...` in `-O ...`.
157 #[derive(Clone, Debug, PartialEq)]
158 pub struct CommaSeparated<T>(pub Vec<T>);
159 
160 impl<T> ValueParserFactory for CommaSeparated<T>
161 where
162     T: WasmtimeOption,
163 {
164     type Parser = CommaSeparatedParser<T>;
165 
value_parser() -> CommaSeparatedParser<T>166     fn value_parser() -> CommaSeparatedParser<T> {
167         CommaSeparatedParser(marker::PhantomData)
168     }
169 }
170 
171 #[derive(Clone)]
172 pub struct CommaSeparatedParser<T>(marker::PhantomData<T>);
173 
174 impl<T> TypedValueParser for CommaSeparatedParser<T>
175 where
176     T: WasmtimeOption,
177 {
178     type Value = CommaSeparated<T>;
179 
parse_ref( &self, cmd: &clap::Command, arg: Option<&clap::Arg>, value: &std::ffi::OsStr, ) -> Result<Self::Value, Error>180     fn parse_ref(
181         &self,
182         cmd: &clap::Command,
183         arg: Option<&clap::Arg>,
184         value: &std::ffi::OsStr,
185     ) -> Result<Self::Value, Error> {
186         let val = StringValueParser::new().parse_ref(cmd, arg, value)?;
187 
188         let options = T::OPTIONS;
189         let arg = arg.expect("should always have an argument");
190         let arg_long = arg.get_long().expect("should have a long name specified");
191         let arg_short = arg.get_short().expect("should have a short name specified");
192 
193         // Handle `-O help` which dumps all the `-O` options, their messages,
194         // and then exits.
195         if val == "help" {
196             let mut max = 0;
197             for d in options {
198                 max = max.max(d.name.display_string().len() + d.val_help.len());
199             }
200             println!("Available {arg_long} options:\n");
201             for d in options {
202                 print!(
203                     "  -{arg_short} {:>1$}",
204                     d.name.display_string(),
205                     max - d.val_help.len()
206                 );
207                 print!("{}", d.val_help);
208                 print!(" --");
209                 if val == "help" {
210                     for line in d.docs.lines().map(|s| s.trim()) {
211                         if line.is_empty() {
212                             break;
213                         }
214                         print!(" {line}");
215                     }
216                     println!();
217                 } else {
218                     println!();
219                     for line in d.docs.lines().map(|s| s.trim()) {
220                         let line = line.trim();
221                         println!("        {line}");
222                     }
223                 }
224             }
225             println!("\npass `-{arg_short} help-long` to see longer-form explanations");
226             std::process::exit(0);
227         }
228         if val == "help-long" {
229             println!("Available {arg_long} options:\n");
230             for d in options {
231                 println!(
232                     "  -{arg_short} {}{} --",
233                     d.name.display_string(),
234                     d.val_help
235                 );
236                 println!();
237                 for line in d.docs.lines().map(|s| s.trim()) {
238                     let line = line.trim();
239                     println!("        {line}");
240                 }
241             }
242             std::process::exit(0);
243         }
244 
245         let mut result = Vec::new();
246         for val in val.split(',') {
247             // Split `k=v` into `k` and `v` where `v` is optional
248             let mut iter = val.splitn(2, '=');
249             let key = iter.next().unwrap();
250             let key_val = iter.next();
251 
252             // Find `key` within `T::OPTIONS`
253             let option = options
254                 .iter()
255                 .filter_map(|d| match d.name {
256                     OptName::Name(s) => {
257                         let s = s.replace('_', "-");
258                         if s == key { Some((d, s)) } else { None }
259                     }
260                     OptName::Prefix(s) => {
261                         let name = key.strip_prefix(s)?.strip_prefix("-")?;
262                         Some((d, name.to_string()))
263                     }
264                 })
265                 .next();
266 
267             let (desc, key) = match option {
268                 Some(pair) => pair,
269                 None => {
270                     let err = Error::raw(
271                         ErrorKind::InvalidValue,
272                         format!("unknown -{arg_short} / --{arg_long} option: {key}\n"),
273                     );
274                     return Err(err.with_cmd(cmd));
275                 }
276             };
277 
278             result.push((desc.parse)(&key, key_val).map_err(|e| {
279                 Error::raw(
280                     ErrorKind::InvalidValue,
281                     format!("failed to parse -{arg_short} option `{val}`: {e:?}\n"),
282                 )
283                 .with_cmd(cmd)
284             })?)
285         }
286 
287         Ok(CommaSeparated(result))
288     }
289 }
290 
291 /// Helper trait used by `CommaSeparated` which contains a list of all options
292 /// supported by the option group.
293 pub trait WasmtimeOption: Sized + Send + Sync + Clone + 'static {
294     const OPTIONS: &'static [OptionDesc<Self>];
295 }
296 
297 pub struct OptionDesc<T> {
298     pub name: OptName,
299     pub docs: &'static str,
300     pub parse: fn(&str, Option<&str>) -> Result<T>,
301     pub val_help: &'static str,
302 }
303 
304 pub enum OptName {
305     /// A named option. Note that the `str` here uses `_` instead of `-` because
306     /// it's derived from Rust syntax.
307     Name(&'static str),
308 
309     /// A prefixed option which strips the specified `name`, then `-`.
310     Prefix(&'static str),
311 }
312 
313 impl OptName {
display_string(&self) -> String314     fn display_string(&self) -> String {
315         match self {
316             OptName::Name(s) => s.replace('_', "-"),
317             OptName::Prefix(s) => format!("{s}-<KEY>"),
318         }
319     }
320 }
321 
322 /// A helper trait for all types of options that can be parsed. This is what
323 /// actually parses the `=val` in `key=val`
324 pub trait WasmtimeOptionValue: Sized {
325     /// Help text for the value to be specified.
326     const VAL_HELP: &'static str;
327 
328     /// Parses the provided value, if given, returning an error on failure.
parse(val: Option<&str>) -> Result<Self>329     fn parse(val: Option<&str>) -> Result<Self>;
330 
331     /// Write the value to `f` that would parse to `self`.
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result332     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
333 }
334 
335 impl WasmtimeOptionValue for String {
336     const VAL_HELP: &'static str = "=val";
parse(val: Option<&str>) -> Result<Self>337     fn parse(val: Option<&str>) -> Result<Self> {
338         match val {
339             Some(val) => Ok(val.to_string()),
340             None => bail!("value must be specified with `key=val` syntax"),
341         }
342     }
343 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result344     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345         f.write_str(self)
346     }
347 }
348 
349 impl WasmtimeOptionValue for PathBuf {
350     const VAL_HELP: &'static str = "=path";
parse(val: Option<&str>) -> Result<Self>351     fn parse(val: Option<&str>) -> Result<Self> {
352         match val {
353             Some(val) => Ok(PathBuf::from_str(val)?),
354             None => bail!("value must be specified with key=val syntax"),
355         }
356     }
357 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result358     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359         write!(f, "{self:?}")
360     }
361 }
362 
363 impl WasmtimeOptionValue for u32 {
364     const VAL_HELP: &'static str = "=N";
parse(val: Option<&str>) -> Result<Self>365     fn parse(val: Option<&str>) -> Result<Self> {
366         let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
367         match val.strip_prefix("0x") {
368             Some(hex) => Ok(u32::from_str_radix(hex, 16)?),
369             None => Ok(val.parse()?),
370         }
371     }
372 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result373     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374         write!(f, "{self}")
375     }
376 }
377 
378 impl WasmtimeOptionValue for u64 {
379     const VAL_HELP: &'static str = "=N";
parse(val: Option<&str>) -> Result<Self>380     fn parse(val: Option<&str>) -> Result<Self> {
381         let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
382         match val.strip_prefix("0x") {
383             Some(hex) => Ok(u64::from_str_radix(hex, 16)?),
384             None => Ok(val.parse()?),
385         }
386     }
387 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result388     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389         write!(f, "{self}")
390     }
391 }
392 
393 impl WasmtimeOptionValue for usize {
394     const VAL_HELP: &'static str = "=N";
parse(val: Option<&str>) -> Result<Self>395     fn parse(val: Option<&str>) -> Result<Self> {
396         let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
397         match val.strip_prefix("0x") {
398             Some(hex) => Ok(usize::from_str_radix(hex, 16)?),
399             None => Ok(val.parse()?),
400         }
401     }
402 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result403     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404         write!(f, "{self}")
405     }
406 }
407 
408 impl WasmtimeOptionValue for bool {
409     const VAL_HELP: &'static str = "[=y|n]";
parse(val: Option<&str>) -> Result<Self>410     fn parse(val: Option<&str>) -> Result<Self> {
411         match val {
412             None | Some("y") | Some("yes") | Some("true") => Ok(true),
413             Some("n") | Some("no") | Some("false") => Ok(false),
414             Some(s) => bail!("unknown boolean flag `{s}`, only yes,no,<nothing> accepted"),
415         }
416     }
417 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result418     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419         if *self {
420             f.write_str("y")
421         } else {
422             f.write_str("n")
423         }
424     }
425 }
426 
427 impl WasmtimeOptionValue for Duration {
428     const VAL_HELP: &'static str = "=N|Ns|Nms|..";
parse(val: Option<&str>) -> Result<Duration>429     fn parse(val: Option<&str>) -> Result<Duration> {
430         let s = String::parse(val)?;
431         // assume an integer without a unit specified is a number of seconds ...
432         if let Ok(val) = s.parse() {
433             return Ok(Duration::from_secs(val));
434         }
435 
436         if let Some(num) = s.strip_suffix("s") {
437             if let Ok(val) = num.parse() {
438                 return Ok(Duration::from_secs(val));
439             }
440         }
441         if let Some(num) = s.strip_suffix("ms") {
442             if let Ok(val) = num.parse() {
443                 return Ok(Duration::from_millis(val));
444             }
445         }
446         if let Some(num) = s.strip_suffix("us").or(s.strip_suffix("μs")) {
447             if let Ok(val) = num.parse() {
448                 return Ok(Duration::from_micros(val));
449             }
450         }
451         if let Some(num) = s.strip_suffix("ns") {
452             if let Ok(val) = num.parse() {
453                 return Ok(Duration::from_nanos(val));
454             }
455         }
456 
457         bail!("failed to parse duration: {s}")
458     }
459 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result460     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461         let subsec = self.subsec_nanos();
462         if subsec == 0 {
463             write!(f, "{}s", self.as_secs())
464         } else if subsec % 1_000 == 0 {
465             write!(f, "{}μs", self.as_micros())
466         } else if subsec % 1_000_000 == 0 {
467             write!(f, "{}ms", self.as_millis())
468         } else {
469             write!(f, "{}ns", self.as_nanos())
470         }
471     }
472 }
473 
474 impl WasmtimeOptionValue for wasmtime::OptLevel {
475     const VAL_HELP: &'static str = "=0|1|2|s";
parse(val: Option<&str>) -> Result<Self>476     fn parse(val: Option<&str>) -> Result<Self> {
477         match String::parse(val)?.as_str() {
478             "0" => Ok(wasmtime::OptLevel::None),
479             "1" => Ok(wasmtime::OptLevel::Speed),
480             "2" => Ok(wasmtime::OptLevel::Speed),
481             "s" => Ok(wasmtime::OptLevel::SpeedAndSize),
482             other => bail!("unknown optimization level `{other}`, only 0,1,2,s accepted"),
483         }
484     }
485 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result486     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487         match *self {
488             wasmtime::OptLevel::None => f.write_str("0"),
489             wasmtime::OptLevel::Speed => f.write_str("2"),
490             wasmtime::OptLevel::SpeedAndSize => f.write_str("s"),
491             _ => unreachable!(),
492         }
493     }
494 }
495 
496 impl WasmtimeOptionValue for wasmtime::RegallocAlgorithm {
497     const VAL_HELP: &'static str = "=backtracking|single-pass";
parse(val: Option<&str>) -> Result<Self>498     fn parse(val: Option<&str>) -> Result<Self> {
499         match String::parse(val)?.as_str() {
500             "backtracking" => Ok(wasmtime::RegallocAlgorithm::Backtracking),
501             "single-pass" => Ok(wasmtime::RegallocAlgorithm::SinglePass),
502             other => {
503                 bail!("unknown regalloc algorithm`{other}`, only backtracking,single-pass accepted")
504             }
505         }
506     }
507 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result508     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509         match *self {
510             wasmtime::RegallocAlgorithm::Backtracking => f.write_str("backtracking"),
511             wasmtime::RegallocAlgorithm::SinglePass => f.write_str("single-pass"),
512             _ => unreachable!(),
513         }
514     }
515 }
516 
517 impl WasmtimeOptionValue for wasmtime::Strategy {
518     const VAL_HELP: &'static str = "=winch|cranelift";
parse(val: Option<&str>) -> Result<Self>519     fn parse(val: Option<&str>) -> Result<Self> {
520         match String::parse(val)?.as_str() {
521             "cranelift" => Ok(wasmtime::Strategy::Cranelift),
522             "winch" => Ok(wasmtime::Strategy::Winch),
523             other => bail!("unknown compiler `{other}` only `cranelift` and `winch` accepted",),
524         }
525     }
526 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result527     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528         match *self {
529             wasmtime::Strategy::Cranelift => f.write_str("cranelift"),
530             wasmtime::Strategy::Winch => f.write_str("winch"),
531             _ => unreachable!(),
532         }
533     }
534 }
535 
536 impl WasmtimeOptionValue for wasmtime::Collector {
537     const VAL_HELP: &'static str = "=drc|null";
parse(val: Option<&str>) -> Result<Self>538     fn parse(val: Option<&str>) -> Result<Self> {
539         match String::parse(val)?.as_str() {
540             "drc" => Ok(wasmtime::Collector::DeferredReferenceCounting),
541             "null" => Ok(wasmtime::Collector::Null),
542             other => bail!("unknown collector `{other}` only `drc` and `null` accepted",),
543         }
544     }
545 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result546     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547         match *self {
548             wasmtime::Collector::DeferredReferenceCounting => f.write_str("drc"),
549             wasmtime::Collector::Null => f.write_str("null"),
550             _ => unreachable!(),
551         }
552     }
553 }
554 
555 impl WasmtimeOptionValue for wasmtime::Enabled {
556     const VAL_HELP: &'static str = "[=y|n|auto]";
parse(val: Option<&str>) -> Result<Self>557     fn parse(val: Option<&str>) -> Result<Self> {
558         match val {
559             None | Some("y") | Some("yes") | Some("true") => Ok(wasmtime::Enabled::Yes),
560             Some("n") | Some("no") | Some("false") => Ok(wasmtime::Enabled::No),
561             Some("auto") => Ok(wasmtime::Enabled::Auto),
562             Some(s) => bail!("unknown flag `{s}`, only yes,no,auto,<nothing> accepted"),
563         }
564     }
565 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result566     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
567         match *self {
568             wasmtime::Enabled::Yes => f.write_str("y"),
569             wasmtime::Enabled::No => f.write_str("n"),
570             wasmtime::Enabled::Auto => f.write_str("auto"),
571         }
572     }
573 }
574 
575 impl WasmtimeOptionValue for WasiNnGraph {
576     const VAL_HELP: &'static str = "=<format>::<dir>";
parse(val: Option<&str>) -> Result<Self>577     fn parse(val: Option<&str>) -> Result<Self> {
578         let val = String::parse(val)?;
579         let mut parts = val.splitn(2, "::");
580         Ok(WasiNnGraph {
581             format: parts.next().unwrap().to_string(),
582             dir: match parts.next() {
583                 Some(part) => part.into(),
584                 None => bail!("graph does not contain `::` separator for directory"),
585             },
586         })
587     }
588 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result589     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590         write!(f, "{}::{}", self.format, self.dir)
591     }
592 }
593 
594 impl WasmtimeOptionValue for KeyValuePair {
595     const VAL_HELP: &'static str = "=<name>=<val>";
parse(val: Option<&str>) -> Result<Self>596     fn parse(val: Option<&str>) -> Result<Self> {
597         let val = String::parse(val)?;
598         let mut parts = val.splitn(2, "=");
599         Ok(KeyValuePair {
600             key: parts.next().unwrap().to_string(),
601             value: match parts.next() {
602                 Some(part) => part.into(),
603                 None => "".to_string(),
604             },
605         })
606     }
607 
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result608     fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609         f.write_str(&self.key)?;
610         if !self.value.is_empty() {
611             f.write_str("=")?;
612             f.write_str(&self.value)?;
613         }
614         Ok(())
615     }
616 }
617 
618 pub trait OptionContainer<T> {
push(&mut self, val: T)619     fn push(&mut self, val: T);
get<'a>(&'a self) -> impl Iterator<Item = &'a T> where T: 'a620     fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
621     where
622         T: 'a;
623 }
624 
625 impl<T> OptionContainer<T> for Option<T> {
push(&mut self, val: T)626     fn push(&mut self, val: T) {
627         *self = Some(val);
628     }
get<'a>(&'a self) -> impl Iterator<Item = &'a T> where T: 'a,629     fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
630     where
631         T: 'a,
632     {
633         self.iter()
634     }
635 }
636 
637 impl<T> OptionContainer<T> for Vec<T> {
push(&mut self, val: T)638     fn push(&mut self, val: T) {
639         Vec::push(self, val);
640     }
get<'a>(&'a self) -> impl Iterator<Item = &'a T> where T: 'a,641     fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
642     where
643         T: 'a,
644     {
645         self.iter()
646     }
647 }
648 
649 // Used to parse toml values into string so that we can reuse the `WasmtimeOptionValue::parse`
650 // for parsing toml values the same way we parse command line values.
651 //
652 // Used for wasmtime::Strategy, wasmtime::Collector, wasmtime::OptLevel, wasmtime::RegallocAlgorithm
653 struct ToStringVisitor {}
654 
655 impl<'de> Visitor<'de> for ToStringVisitor {
656     type Value = String;
657 
expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result658     fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
659         write!(formatter, "&str, u64, or i64")
660     }
661 
visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: de::Error,662     fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
663     where
664         E: de::Error,
665     {
666         Ok(s.to_owned())
667     }
668 
visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: de::Error,669     fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
670     where
671         E: de::Error,
672     {
673         Ok(v.to_string())
674     }
675 
visit_i64<E>(self, v: i64) -> Result<Self::Value, E> where E: de::Error,676     fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
677     where
678         E: de::Error,
679     {
680         Ok(v.to_string())
681     }
682 }
683 
684 // Deserializer that uses the `WasmtimeOptionValue::parse` to parse toml values
cli_parse_wrapper<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error> where T: WasmtimeOptionValue, D: serde::Deserializer<'de>,685 pub(crate) fn cli_parse_wrapper<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
686 where
687     T: WasmtimeOptionValue,
688     D: serde::Deserializer<'de>,
689 {
690     let to_string_visitor = ToStringVisitor {};
691     let str = deserializer.deserialize_any(to_string_visitor)?;
692 
693     T::parse(Some(&str))
694         .map(Some)
695         .map_err(serde::de::Error::custom)
696 }
697 
698 #[cfg(test)]
699 mod tests {
700     use super::WasmtimeOptionValue;
701 
702     #[test]
numbers_with_underscores()703     fn numbers_with_underscores() {
704         assert!(<u32 as WasmtimeOptionValue>::parse(Some("123")).is_ok_and(|v| v == 123));
705         assert!(<u32 as WasmtimeOptionValue>::parse(Some("1_2_3")).is_ok_and(|v| v == 123));
706     }
707 }
708