1use crate::prelude::*;
2
3use clap::{Parser, Subcommand, ValueEnum};
4use std::fmt;
5
6pub use hax_frontend_exporter_options::*;
7pub mod extension;
8use extension::Extension;
9
10#[derive_group(Serializers)]
11#[derive(JsonSchema, Debug, Clone, Eq, PartialEq)]
12pub enum DebugEngineMode {
13 File(PathOrDash),
14 Interactive,
15}
16
17impl std::convert::From<&str> for DebugEngineMode {
18 fn from(s: &str) -> Self {
19 match s {
20 "i" | "interactively" => DebugEngineMode::Interactive,
21 s => DebugEngineMode::File(s.strip_prefix("file:").unwrap_or(s).into()),
22 }
23 }
24}
25
26#[derive_group(Serializers)]
27#[derive(JsonSchema, Debug, Clone, Default)]
28pub struct ForceCargoBuild {
29 pub data: u64,
30}
31
32impl std::convert::From<&str> for ForceCargoBuild {
33 fn from(s: &str) -> Self {
34 use std::time::{SystemTime, UNIX_EPOCH};
35 if s == "false" {
36 let data = SystemTime::now()
37 .duration_since(UNIX_EPOCH)
38 .map(|r| r.as_millis())
39 .unwrap_or(0);
40 ForceCargoBuild { data: data as u64 }
41 } else {
42 ForceCargoBuild::default()
43 }
44 }
45}
46
47#[derive_group(Serializers)]
48#[derive(Debug, Clone, JsonSchema, Eq, PartialEq)]
49pub enum PathOrDash {
50 Dash,
51 Path(PathBuf),
52}
53
54impl std::convert::From<&str> for PathOrDash {
55 fn from(s: &str) -> Self {
56 match s {
57 "-" => PathOrDash::Dash,
58 _ => PathOrDash::Path(PathBuf::from(s)),
59 }
60 }
61}
62
63impl PathOrDash {
64 pub fn open_or_stdout(&self) -> Box<dyn std::io::Write> {
65 use std::io::BufWriter;
66 match self {
67 PathOrDash::Dash => Box::new(BufWriter::new(std::io::stdout())),
68 PathOrDash::Path(path) => {
69 Box::new(BufWriter::new(std::fs::File::create(&path).unwrap()))
70 }
71 }
72 }
73 pub fn map_path<F: FnOnce(&Path) -> PathBuf>(&self, f: F) -> Self {
74 match self {
75 PathOrDash::Path(path) => PathOrDash::Path(f(path)),
76 PathOrDash::Dash => PathOrDash::Dash,
77 }
78 }
79}
80
81fn absolute_path(path: impl AsRef<std::path::Path>) -> std::io::Result<std::path::PathBuf> {
82 use path_clean::PathClean;
83 let path = path.as_ref();
84
85 let absolute_path = if path.is_absolute() {
86 path.to_path_buf()
87 } else {
88 std::env::current_dir()?.join(path)
89 }
90 .clean();
91
92 Ok(absolute_path)
93}
94
95pub trait NormalizePaths {
96 fn normalize_paths(&mut self);
97}
98
99impl NormalizePaths for PathBuf {
100 fn normalize_paths(&mut self) {
101 *self = absolute_path(&self).unwrap();
102 }
103}
104impl NormalizePaths for PathOrDash {
105 fn normalize_paths(&mut self) {
106 match self {
107 PathOrDash::Path(p) => p.normalize_paths(),
108 PathOrDash::Dash => (),
109 }
110 }
111}
112
113#[derive_group(Serializers)]
114#[derive(JsonSchema, Parser, Debug, Hash, Clone, Eq, PartialEq)]
115pub struct ProVerifOptions {
116 #[arg(
125 long,
126 value_parser = parse_inclusion_clause,
127 value_delimiter = ' ',
128 allow_hyphen_values(true)
129 )]
130 pub assume_items: Vec<InclusionClause>,
131}
132
133impl ProVerifOptions {
134 pub fn flags(&self) -> Vec<String> {
138 if self.assume_items.is_empty() {
139 return Vec::new();
140 }
141 std::iter::once("--assume-items".to_string())
142 .chain(self.assume_items.iter().map(ToString::to_string))
143 .collect()
144 }
145}
146
147const FSTAR_DEFAULT_Z3RLIMIT: u32 = 15;
150const FSTAR_DEFAULT_FUEL: u32 = 0;
151const FSTAR_DEFAULT_IFUEL: u32 = 1;
152const FSTAR_DEFAULT_LINE_WIDTH: u16 = 100;
153
154#[derive_group(Serializers)]
155#[derive(JsonSchema, Parser, Debug, Hash, Clone, Eq, PartialEq)]
156pub struct FStarOptions {
157 #[arg(long, default_value_t = FSTAR_DEFAULT_Z3RLIMIT)]
159 pub z3rlimit: u32,
160 #[arg(long, default_value_t = FSTAR_DEFAULT_FUEL)]
162 pub fuel: u32,
163 #[arg(long, default_value_t = FSTAR_DEFAULT_IFUEL)]
165 pub ifuel: u32,
166 #[arg(
179 long,
180 value_parser = parse_inclusion_clause,
181 value_delimiter = ' ',
182 allow_hyphen_values(true)
183 )]
184 pub interfaces: Vec<InclusionClause>,
185
186 #[arg(long, default_value_t = FSTAR_DEFAULT_LINE_WIDTH, env = "HAX_FSTAR_LINE_WIDTH")]
187 pub line_width: u16,
188}
189
190impl FStarOptions {
191 pub fn defaults() -> Self {
195 Self {
196 z3rlimit: FSTAR_DEFAULT_Z3RLIMIT,
197 fuel: FSTAR_DEFAULT_FUEL,
198 ifuel: FSTAR_DEFAULT_IFUEL,
199 interfaces: Vec::new(),
200 line_width: FSTAR_DEFAULT_LINE_WIDTH,
201 }
202 }
203
204 pub fn flags(&self) -> Vec<String> {
208 let mut flags = vec![
209 format!("--z3rlimit={}", self.z3rlimit),
210 format!("--fuel={}", self.fuel),
211 format!("--ifuel={}", self.ifuel),
212 format!("--line-width={}", self.line_width),
213 ];
214 if !self.interfaces.is_empty() {
215 flags.push("--interfaces".to_string());
216 flags.extend(self.interfaces.iter().map(ToString::to_string));
217 }
218 flags
219 }
220}
221
222#[derive_group(Serializers)]
227#[derive(JsonSchema, Debug, Clone, Hash, Eq, PartialEq, Default)]
228pub struct LeanScenarioOptions {
229 pub package_name: Option<String>,
231 pub project_files: Option<bool>,
233 pub include: Vec<String>,
235 pub exclude: Vec<String>,
237 pub opaque: Vec<String>,
240 pub charon_args: Vec<String>,
242 pub aeneas_args: Vec<String>,
244 pub cargo_args: Vec<String>,
247}
248
249impl LeanScenarioOptions {
250 pub fn selection_flags(&self) -> Vec<String> {
254 [
255 ("start-from", &self.include),
256 ("exclude", &self.exclude),
257 ("opaque", &self.opaque),
258 ]
259 .into_iter()
260 .flat_map(|(flag, patterns)| {
261 patterns
262 .iter()
263 .map(move |pattern| format!("--{flag}={pattern}"))
264 })
265 .collect()
266 }
267}
268
269#[derive_group(Serializers)]
270#[derive(JsonSchema, Parser, Debug, Clone, Hash, Eq, PartialEq)]
271#[command(after_help = "\
272TOOLS:
273 This backend runs `charon`, then `aeneas`, and generates a Lean proof project.
274 Tool versions are managed by hax: each tool resolves through, in order, the
275 project's `hax.toml` (member crate, then workspace root) and the built-in
276 default version this release was tested with, and is downloaded into the
277 tool cache on demand with checksum verification.
278
279 Inspect the active versions and their sources with `cargo hax tools show`;
280 pre-install them with `cargo hax tools install`.
281
282INVOCATION:
283 The tools are run with some fixed flags (to which any --charon-args/--aeneas-args
284 are appended). Pass `-v` (`cargo hax into -v lean`) to print the exact command
285 before each tool runs.
286
287 Overriding a flag that controls where output is written (aeneas's -backend,
288 -dest, -subdir, or -split-files) may break the extraction or the generated
289 proof project. Charon's --dest-file is reserved and rejected: aeneas is always
290 run on the LLBC file hax chooses. Overriding -dest or -subdir additionally disables
291 the generation and checking of the Lean package files and the clearing of stale
292 extraction files, since hax no longer knows the package layout. Committing
293 `project-files = false` in `hax.toml` disables the package files for every
294 invocation.
295
296 To use a binary you built yourself, commit a `path` entry for the tool in
297 `hax.toml` instead of a version.")]
298pub struct LeanOptions {
299 #[arg(long)]
303 pub charon_args: Option<String>,
304
305 #[arg(long)]
308 pub aeneas_args: Option<String>,
309
310 #[clap(skip)]
312 pub scenario: LeanScenarioOptions,
313}
314
315#[derive_group(Serializers)]
316#[derive(JsonSchema, Subcommand, Debug, Clone, Hash, Eq, PartialEq)]
317pub enum Backend {
318 Fstar(FStarOptions),
320 LegacyLean,
322 Lean(LeanOptions),
324 Coq,
326 Ssprove,
328 Easycrypt,
330 #[clap(alias("proverif"))]
332 ProVerif(ProVerifOptions),
333 #[clap(hide = true)]
335 Rust,
336 #[clap(hide = true)]
339 GenerateRustEngineNames,
340 Debugger {
342 #[arg(long, short)]
343 interactive: bool,
344 },
345}
346
347impl fmt::Display for Backend {
348 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349 BackendName::from(self).fmt(f)
350 }
351}
352
353#[derive_group(Serializers)]
354#[derive(JsonSchema, Debug, Hash, Clone, Eq, PartialEq)]
355pub enum DepsKind {
356 Transitive,
357 Shallow,
358 None,
359}
360
361#[derive_group(Serializers)]
362#[derive(JsonSchema, Debug, Hash, Clone, Eq, PartialEq)]
363pub enum InclusionKind {
364 Included(DepsKind),
366 SignatureOnly,
367 Excluded,
368}
369
370#[derive_group(Serializers)]
371#[derive(JsonSchema, Debug, Hash, Clone, Eq, PartialEq)]
372pub struct InclusionClause {
373 pub kind: InclusionKind,
374 pub namespace: Namespace,
375}
376
377const PREFIX_INCLUDED_TRANSITIVE: &str = "+";
378const PREFIX_INCLUDED_SHALLOW: &str = "+~";
379const PREFIX_INCLUDED_NONE: &str = "+!";
380const PREFIX_SIGNATURE_ONLY: &str = "+:";
381const PREFIX_EXCLUDED: &str = "-";
382
383impl ToString for InclusionClause {
384 fn to_string(&self) -> String {
385 let kind = match self.kind {
386 InclusionKind::Included(DepsKind::Transitive) => PREFIX_INCLUDED_TRANSITIVE,
387 InclusionKind::Included(DepsKind::Shallow) => PREFIX_INCLUDED_SHALLOW,
388 InclusionKind::Included(DepsKind::None) => PREFIX_INCLUDED_NONE,
389 InclusionKind::SignatureOnly => PREFIX_SIGNATURE_ONLY,
390 InclusionKind::Excluded => PREFIX_EXCLUDED,
391 };
392 format!("{kind}{}", self.namespace.to_string())
393 }
394}
395
396pub fn parse_inclusion_clause(
397 s: &str,
398) -> Result<InclusionClause, Box<dyn std::error::Error + Send + Sync + 'static>> {
399 let s = s.trim();
400 if s.is_empty() {
401 Err("Expected `-` or `+`, got an empty string")?
402 }
403 let (prefix, namespace) = {
404 let f = |&c: &char| matches!(c, '+' | '-' | '~' | '!' | ':');
405 (
406 s.chars().take_while(f).into_iter().collect::<String>(),
407 s.chars().skip_while(f).into_iter().collect::<String>(),
408 )
409 };
410 let kind = match &prefix[..] {
411 PREFIX_INCLUDED_TRANSITIVE => InclusionKind::Included(DepsKind::Transitive),
412 PREFIX_INCLUDED_SHALLOW => InclusionKind::Included(DepsKind::Shallow),
413 PREFIX_INCLUDED_NONE => InclusionKind::Included(DepsKind::None),
414 PREFIX_SIGNATURE_ONLY => InclusionKind::SignatureOnly,
415 PREFIX_EXCLUDED => InclusionKind::Excluded,
416 prefix => Err(format!(
417 "Expected `+`, `+~`, `+!`, `+:` or `-`, got an `{prefix}`"
418 ))?,
419 };
420 Ok(InclusionClause {
421 kind,
422 namespace: namespace.to_string().into(),
423 })
424}
425
426#[derive_group(Serializers)]
427#[derive(JsonSchema, Parser, Debug, Clone, Eq, PartialEq)]
428pub struct TranslationOptions {
429 #[arg(
463 value_parser = parse_inclusion_clause,
464 value_delimiter = ' ',
465 )]
466 #[arg(short, allow_hyphen_values(true))]
467 pub include_namespaces: Vec<InclusionClause>,
468}
469
470#[derive_group(Serializers)]
471#[derive(JsonSchema, Parser, Debug, Clone, Eq, PartialEq)]
472pub struct BackendOptions<E: Extension> {
473 #[command(subcommand)]
474 pub backend: Backend,
475
476 #[arg(long = "dry-run")]
479 pub dry_run: bool,
480
481 #[arg(short, long, action = clap::ArgAction::Count)]
483 pub verbose: u8,
484
485 #[arg(long)]
488 pub stats: bool,
489
490 #[arg(long)]
493 pub profile: bool,
494
495 #[arg(long)]
500 #[clap(hide = true)]
501 pub prune_haxmeta: Option<String>,
502
503 #[arg(short, long = "debug-engine")]
517 pub debug_engine: Option<DebugEngineMode>,
518
519 #[arg(long)]
527 pub extract_type_aliases: bool,
528
529 #[command(flatten)]
530 pub translation_options: TranslationOptions,
531
532 #[arg(long)]
535 pub output_dir: Option<PathBuf>,
536
537 #[group(flatten)]
538 pub cli_extension: E::BackendOptions,
539}
540
541#[derive_group(Serializers)]
545#[derive(JsonSchema, Parser, Debug, Clone, Default, Eq, PartialEq)]
546pub struct CargoHermeticityOptions {
547 #[arg(long)]
549 pub locked: bool,
550
551 #[arg(long)]
553 pub offline: bool,
554
555 #[arg(long)]
557 pub frozen: bool,
558}
559
560impl CargoHermeticityOptions {
561 pub fn flags(&self) -> Vec<String> {
563 [
564 ("--locked", self.locked),
565 ("--offline", self.offline),
566 ("--frozen", self.frozen),
567 ]
568 .into_iter()
569 .filter(|(_, set)| *set)
570 .map(|(flag, _)| flag.to_string())
571 .collect()
572 }
573}
574
575#[derive_group(Serializers)]
576#[derive(JsonSchema, Subcommand, Debug, Clone, Eq, PartialEq)]
577pub enum Command<E: Extension> {
578 #[clap(name = "into")]
583 Backend(BackendOptions<E>),
584
585 Extract {
591 names: Vec<String>,
594
595 #[arg(short = 'p', long = "package")]
598 packages: Vec<String>,
599
600 #[arg(long)]
603 dry_run: bool,
604
605 #[arg(short, long, action = clap::ArgAction::Count)]
607 verbose: u8,
608
609 #[command(flatten)]
610 hermeticity: CargoHermeticityOptions,
611 },
612
613 JSON {
615 #[arg(
617 short,
618 long = "output-file",
619 default_value = "hax_frontend_export.json"
620 )]
621 output_file: PathOrDash,
622 #[arg(
627 value_enum,
628 short,
629 long = "kind",
630 num_args = 0..=3,
631 default_values_t = [ExportBodyKind::Thir]
632 )]
633 kind: Vec<ExportBodyKind>,
634
635 #[arg(long)]
640 use_ids: bool,
641
642 #[arg(short = 'E', long = "include-extra", default_value = "false")]
644 include_extra: bool,
645 },
646
647 #[clap(hide = true)]
650 Serialize {
651 #[arg(
656 value_enum,
657 short,
658 long = "kind",
659 num_args = 0..=3,
660 default_values_t = [ExportBodyKind::Thir]
661 )]
662 kind: Vec<ExportBodyKind>,
663
664 #[arg(short)]
667 backend: Option<BackendName>,
668 },
669
670 #[command(subcommand)]
672 Tools(ToolsCommand),
673
674 #[command(flatten)]
675 CliExtension(E::Command),
676}
677
678#[derive_group(Serializers)]
680#[derive(JsonSchema, Subcommand, Debug, Clone, Eq, PartialEq)]
681pub enum ToolsCommand {
682 Install {
685 spec: Option<String>,
690 #[arg(long)]
693 force: bool,
694 },
695 List {
698 tool: Option<String>,
700 #[arg(long)]
702 installed: bool,
703 #[arg(long)]
705 all: bool,
706 },
707 Show,
710 Remove {
712 spec: String,
715 },
716 Clean,
719 Pin {
722 spec: Option<String>,
727 },
728}
729
730impl<E: Extension> Command<E> {
731 pub fn body_kinds(&self) -> Vec<ExportBodyKind> {
732 match self {
733 Command::JSON { kind, .. } => kind.clone(),
734 Command::Serialize { kind, .. } => kind.clone(),
735 Command::Backend { .. }
736 | Command::Extract { .. }
737 | Command::Tools { .. }
738 | Command::CliExtension { .. } => {
739 vec![ExportBodyKind::Thir]
740 }
741 }
742 }
743 pub fn backend_name(&self) -> Option<BackendName> {
744 match self {
745 Command::Backend(backend_options) => Some((&backend_options.backend).into()),
746 Command::JSON { .. } => None,
747 Command::Serialize { backend, .. } => backend.clone(),
748 Command::Extract { .. } => None,
749 Command::Tools(_) => None,
750 Command::CliExtension(_) => None,
751 }
752 }
753}
754
755#[derive_group(Serializers)]
756#[derive(JsonSchema, ValueEnum, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
757pub enum ExportBodyKind {
758 Thir,
759 MirBuilt,
760}
761
762#[derive_group(Serializers)]
763#[derive(JsonSchema, Parser, Debug, Clone)]
764#[command(
765 author,
766 version = crate::HAX_VERSION,
767 long_version = concat!("\nversion=", env!("HAX_VERSION"), "\n", "commit=", env!("HAX_GIT_COMMIT_HASH")),
768 name = "hax",
769 about,
770 long_about = None
771)]
772pub struct ExtensibleOptions<E: Extension> {
773 #[arg(default_values = Vec::<&str>::new(), short='C', allow_hyphen_values=true, num_args=1.., long="cargo-args", value_terminator=";")]
778 pub cargo_flags: Vec<String>,
779
780 #[command(subcommand)]
781 pub command: Command<E>,
782
783 #[arg(long="disable-cargo-cache", action=clap::builder::ArgAction::SetFalse)]
785 pub force_cargo_build: ForceCargoBuild,
786
787 #[arg(long = "deps")]
792 pub deps: bool,
793
794 #[arg(long)]
797 #[clap(hide = true)]
798 pub haxmeta: Option<PathBuf>,
799
800 #[arg(long)]
805 pub no_custom_target_directory: bool,
806
807 #[arg(long, default_value = "human")]
810 pub message_format: MessageFormat,
811
812 #[arg(long, env = "HAX_EXPERIMENTAL_FULL_DEF")]
815 pub experimental_full_def: bool,
816
817 #[group(flatten)]
818 pub extension: E::Options,
819}
820
821pub type Options = ExtensibleOptions<()>;
822
823#[derive_group(Serializers)]
824#[derive(JsonSchema, ValueEnum, Debug, Clone, Copy, Eq, PartialEq)]
825pub enum MessageFormat {
826 Human,
827 Json,
828}
829
830impl<E: Extension> NormalizePaths for Command<E> {
831 fn normalize_paths(&mut self) {
832 use Command::*;
833 match self {
834 JSON { output_file, .. } => output_file.normalize_paths(),
835 _ => (),
836 }
837 }
838}
839
840impl NormalizePaths for Options {
841 fn normalize_paths(&mut self) {
842 self.command.normalize_paths()
843 }
844}
845
846impl From<Options> for hax_frontend_exporter_options::Options {
847 fn from(_opts: Options) -> hax_frontend_exporter_options::Options {
848 hax_frontend_exporter_options::Options {
849 inline_anon_consts: true,
850 bounds_options: hax_frontend_exporter_options::BoundsOptions {
851 resolve_destruct: false,
852 prune_sized: true,
853 },
854 item_ref_use_concrete_impl: false,
855 }
856 }
857}
858
859#[derive_group(Serializers)]
861#[derive(JsonSchema, Debug, Clone)]
862pub struct ExporterOptions {
863 pub deps: bool,
864 pub force_cargo_build: ForceCargoBuild,
865 pub backend: Option<BackendName>,
867 pub body_kinds: Vec<ExportBodyKind>,
868 pub experimental_full_def: bool,
869}
870
871#[derive_group(Serializers)]
872#[derive(JsonSchema, ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Hash)]
873pub enum BackendName {
874 Fstar,
875 Coq,
876 Ssprove,
877 Easycrypt,
878 #[clap(alias("proverif"))]
879 ProVerif,
880 LegacyLean,
881 Lean,
882 Rust,
883 GenerateRustEngineNames,
884 Debugger,
885}
886
887impl BackendName {
888 pub fn output_subdir(self) -> Option<&'static str> {
892 match self {
893 Self::Lean => None,
894 _ => Some("extraction"),
895 }
896 }
897
898 pub fn iter() -> impl Iterator<Item = Self> {
899 [
900 Self::Fstar,
901 Self::Coq,
902 Self::Ssprove,
903 Self::Easycrypt,
904 Self::ProVerif,
905 Self::LegacyLean,
906 Self::Lean,
907 Self::Rust,
908 Self::GenerateRustEngineNames,
909 ]
910 .into_iter()
911 }
912}
913
914impl fmt::Display for BackendName {
915 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
916 let name = match self {
917 BackendName::Fstar => "fstar",
918 BackendName::Coq => "coq",
919 BackendName::Ssprove => "ssprove",
920 BackendName::Easycrypt => "easycrypt",
921 BackendName::ProVerif => "proverif",
922 BackendName::LegacyLean => "legacy-lean",
923 BackendName::Lean => "lean",
924 BackendName::Rust => "rust",
925 BackendName::GenerateRustEngineNames => "generate_rust_engine_names",
926 BackendName::Debugger => "debugger",
927 };
928 write!(f, "{name}")
929 }
930}
931
932impl From<&Options> for ExporterOptions {
933 fn from(options: &Options) -> Self {
934 ExporterOptions {
935 deps: options.deps,
936 force_cargo_build: options.force_cargo_build.clone(),
937 backend: options.command.backend_name(),
938 body_kinds: options.command.body_kinds(),
939 experimental_full_def: options.experimental_full_def,
940 }
941 }
942}
943
944impl From<&Backend> for BackendName {
945 fn from(backend: &Backend) -> Self {
946 match backend {
947 Backend::Fstar { .. } => BackendName::Fstar,
948 Backend::Coq { .. } => BackendName::Coq,
949 Backend::Ssprove { .. } => BackendName::Ssprove,
950 Backend::Easycrypt { .. } => BackendName::Easycrypt,
951 Backend::ProVerif { .. } => BackendName::ProVerif,
952 Backend::LegacyLean { .. } => BackendName::LegacyLean,
953 Backend::Lean { .. } => BackendName::Lean,
954 Backend::Rust { .. } => BackendName::Rust,
955 Backend::GenerateRustEngineNames { .. } => BackendName::GenerateRustEngineNames,
956 Backend::Debugger { .. } => BackendName::Debugger,
957 }
958 }
959}
960
961pub const ENV_VAR_OPTIONS_FRONTEND: &str = "DRIVER_HAX_FRONTEND_OPTS";
962pub const ENV_VAR_OPTIONS_FULL: &str = "DRIVER_HAX_FRONTEND_FULL_OPTS";
963
964#[cfg(test)]
965mod tests {
966 use super::*;
967
968 #[test]
972 fn fstar_flags_render_every_field() {
973 let options = FStarOptions {
974 z3rlimit: 111,
975 fuel: 222,
976 ifuel: 333,
977 interfaces: vec![parse_inclusion_clause("+**::foo").unwrap()],
978 line_width: 444,
979 };
980 let FStarOptions {
981 z3rlimit,
982 fuel,
983 ifuel,
984 interfaces,
985 line_width,
986 } = &options;
987 let flags = options.flags().join(" ");
988 assert!(flags.contains(&format!("--z3rlimit={z3rlimit}")), "{flags}");
989 assert!(flags.contains(&format!("--fuel={fuel}")), "{flags}");
990 assert!(flags.contains(&format!("--ifuel={ifuel}")), "{flags}");
991 assert!(
992 flags.contains(&format!("--line-width={line_width}")),
993 "{flags}"
994 );
995 assert!(flags.contains("--interfaces"), "{flags}");
996 for clause in interfaces {
997 assert!(flags.contains(&clause.to_string()), "{flags}");
998 }
999 }
1000
1001 #[test]
1002 fn proverif_flags_render_every_field() {
1003 let options = ProVerifOptions {
1004 assume_items: vec![parse_inclusion_clause("+**::bar").unwrap()],
1005 };
1006 let ProVerifOptions { assume_items } = &options;
1007 let flags = options.flags().join(" ");
1008 assert!(flags.contains("--assume-items"), "{flags}");
1009 for clause in assume_items {
1010 assert!(flags.contains(&clause.to_string()), "{flags}");
1011 }
1012 }
1013
1014 #[test]
1017 fn only_the_lean_backend_has_no_extraction_subdir() {
1018 for backend in BackendName::iter() {
1019 let subdir = backend.output_subdir();
1020 if backend == BackendName::Lean {
1021 assert_eq!(subdir, None, "{backend}");
1022 } else {
1023 assert_eq!(subdir, Some("extraction"), "{backend}");
1024 }
1025 }
1026 }
1027}