Something went wrong. Try again.
The Monad language. Dependent types, functional programming compiled with LLVM. Hobby project. monad-lang.org
dependent-types language compiler programming-language functional-programming
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398/// Smoke tests for `cli/src/main.mo`'s `Command.from_args` argv parser.////// This used to say `cargo run -- run cli/src/main.mo ...` (actually/// executing `main`) hit a separate, pre-existing `instance-Monad-IO/// not found` failure — no longer reproduces (confirmed via many real/// `build`/`check`/`pretty`/`test` invocations, 2026-08-19); whatever/// that was has since been fixed elsewhere, or this repro was itself/// stale. Kept testing `Command.from_args` directly anyway (isolating/// argv-parsing from everything downstream is still the more precise/// unit of test coverage, real end-to-end runs notwithstanding).use lib::main {*}#[test]def test_from_args_build_positional_name : Bool := match Command.from_args ["build", "a.mo", "myname"] { Command.build path out_name _bin verbose debug _no_cache _target => Path.to_string path == "a.mo" && Path.to_string out_name == "myname" && verbose == false && debug == true, _ => false, }#[test]def test_from_args_build_default_name : Bool := match Command.from_args ["build", "a.mo"] { Command.build path out_name _bin verbose debug _no_cache _target => Path.to_string path == "a.mo" && Path.to_string out_name == "source" && verbose == false && debug == true, _ => false, }#[test]def test_from_args_build_output_flag : Bool := match Command.from_args ["build", "a.mo", "--output", "out", "--verbose"] { Command.build path out_name _bin verbose debug _no_cache _target => Path.to_string path == "a.mo" && Path.to_string out_name == "out" && verbose == true && debug == true, _ => false, }#[test]def test_from_args_build_short_flags : Bool := match Command.from_args ["build", "a.mo", "-o", "out", "-v"] { Command.build path out_name _bin verbose debug _no_cache _target => Path.to_string path == "a.mo" && Path.to_string out_name == "out" && verbose == true && debug == true, _ => false, }#[test]def test_from_args_build_debug_flag : Bool := match Command.from_args ["build", "a.mo", "--debug"] { Command.build path out_name _bin verbose debug _no_cache _target => Path.to_string path == "a.mo" && verbose == false && debug == true, _ => false, }#[test]def test_from_args_build_debug_short_flag : Bool := match Command.from_args ["build", "a.mo", "-g", "-v"] { Command.build path out_name _bin verbose debug _no_cache _target => Path.to_string path == "a.mo" && verbose == true && debug == true, _ => false, }/// Debug info is ON by default (rustc's own dev-profile default);/// `--release` is how you opt out.#[test]def test_from_args_build_release_opts_out_of_debug : Bool := match Command.from_args ["build", "a.mo", "--release"] { Command.build path out_name _bin verbose debug _no_cache _target => Path.to_string path == "a.mo" && verbose == false && debug == false, _ => false, }/// An explicit `--debug` wins over `--release` -- asking twice, with/// the more specific request, is not an error.#[test]def test_from_args_build_debug_beats_release : Bool := match Command.from_args ["build", "a.mo", "--release", "--debug"] { Command.build path out_name _bin verbose debug _no_cache _target => Path.to_string path == "a.mo" && verbose == false && debug == true, _ => false, }/// `monad build` with no path is `monad build .` -- the mote containing/// the working directory, matching `check`/`test`'s own default. It used/// to print usage, which is what made `compile` the one verb with no/// zero-argument form.#[test]def test_from_args_build_with_no_path_defaults_to_the_mote : Bool := match Command.from_args ["build"] { Command.build path out_name _bin verbose debug _no_cache _target => Path.to_string path == "." && Path.to_string out_name == "source" && verbose == false && debug == true, _ => false, }/// `build .` and a bare `build` are one code path, so they must parse to/// the same command.#[test]def test_from_args_build_bare_and_dot_agree : Bool := match Command.from_args ["build"] { Command.build p1 o1 _b1 _v1 _d1 _n1 _target => match Command.from_args ["build", "."] { Command.build p2 o2 _b2 _v2 _d2 _n2 _target => Path.to_string p1 == Path.to_string p2 && Path.to_string o1 == Path.to_string o2, _ => false, }, _ => false, }// `build`'s cache escape hatch, peeled for the same reason `check`'s is:// it is a flag, not a path, and left in the list it would be handed to the// path expander as a filename.#[test]def test_from_args_build_no_cache_flag : Bool := match Command.from_args ["build", "a.mo", "--no-cache"] { Command.build path _out_name _bin _verbose _debug no_cache _target => Path.to_string path == "a.mo" && no_cache == true, _ => false, }// `--bin <name>` is peeled with the other flags for the same reason: left// in the list it would be read as the positional PATH and its name as the// positional output name, so `monad build --bin tool .` would build `.` as// `tool` -- one argument, two meanings, both wrong.#[test]def test_from_args_build_bin_flag : Bool := match Command.from_args ["build", "a.mo", "--bin", "tool"] { Command.build path out_name bin _verbose _debug _no_cache _target => Path.to_string path == "a.mo" && bin == "tool" && Path.to_string out_name == "source", _ => false, }// `--target <triple>` is peeled with the other flags for the same reason// `--bin` is: left in the list, `--target` reads as the positional PATH and// the triple as the output name.#[test]def test_from_args_print_targets : Bool := match Command.from_args ["print-targets"] { Command.print_targets => true, _ => false, }#[test]def test_from_args_build_target_flag : Bool := match Command.from_args ["build", "a.mo", "--target", "aarch64-unknown-linux-gnu"] { Command.build path out_name _bin _verbose _debug _no_cache target => Path.to_string path == "a.mo" && target == "aarch64-unknown-linux-gnu" && Path.to_string out_name == "source", _ => false, }/// And with no `--target` at all it is empty, which `TargetSpec.resolve`/// reads as "this machine" rather than as a target named "".#[test]def test_from_args_build_without_target_is_empty : Bool := match Command.from_args ["build", "a.mo"] { Command.build _path _out_name _bin _verbose _debug _no_cache target => target == "", _ => false, }/// Empty, not a name: "no `--bin` was given" is a different state from/// "`--bin` named something", and only the empty one lets `build_target`/// fall back to "the target that exists".#[test]def test_from_args_build_without_bin_is_empty : Bool := match Command.from_args ["build", "a.mo"] { Command.build _path _out_name bin _verbose _debug _no_cache _target => bin == "", _ => false, }// Named, not merely accepted: a build that did not ask for the hatch must// not get it, or the flag would be indistinguishable from one that is// silently always on.#[test]def test_from_args_build_without_no_cache_is_false : Bool := match Command.from_args ["build", "a.mo"] { Command.build _path _out_name _bin _verbose _debug no_cache _target => no_cache == false, _ => false, }/// The removed verb must not be silently accepted as something else --/// `compile` is now an ordinary unknown word, so it falls to `help`.#[test]def test_from_args_compile_is_no_longer_a_verb : Bool := match Command.from_args ["compile", "a.mo"] { Command.help => true, _ => false, }#[test]def test_from_args_pretty : Bool := match Command.from_args ["pretty", "b.mo"] { Command.pretty path => path == "b.mo", _ => false, }#[test]def test_from_args_check : Bool := match Command.from_args ["check", "a.mo", "b.mo", "-v"] { Command.check files opts => files == ["a.mo", "b.mo"] && opts.verbose == true && opts.workspace == false && opts.no_cache == false && opts.affine == false, _ => false, }#[test]def test_from_args_check_workspace_flag : Bool := match Command.from_args ["check", "--workspace"] { Command.check files opts => List.is_empty files && opts.workspace == true && opts.verbose == false && opts.no_cache == false && opts.affine == false, _ => false, }#[test]def test_from_args_check_workspace_short_flag : Bool := match Command.from_args ["check", "-w", "-v"] { Command.check files opts => List.is_empty files && opts.workspace == true && opts.verbose == true && opts.no_cache == false && opts.affine == false, _ => false, }// The flag must be PEELED, not left among the paths -- otherwise it is// handed to the path expander as a filename.#[test]def test_from_args_check_workspace_flag_not_a_path : Bool := match Command.from_args ["check", "--workspace", "a.mo"] { Command.check files opts => files == ["a.mo"] && opts.workspace == true && opts.no_cache == false && opts.affine == false, _ => false, }// A bare `monad check` no longer means "print help": it means "check the// mote containing the working directory" (and only falls back, at RUN// time and with exit 1, to `no_target_diagnostic` when there is no such// mote). Mirroring `test_from_args_test_no_files_is_a_test_command`,// which Phase 9b made true for `test` too.#[test]def test_from_args_check_no_files_is_a_check_command : Bool := match Command.from_args ["check"] { Command.check files opts => List.is_empty files && opts.workspace == false && opts.verbose == false && opts.no_cache == false && opts.affine == false, _ => false, }// `--no-cache` is peeled for the same reason `--workspace` is: it is a// flag, not a path. Left in the list it would be handed to the path// expander as a filename, where it would surface as a file that does not// exist rather than as a flag that was ignored.#[test]def test_from_args_check_no_cache_flag : Bool := match Command.from_args ["check", "--no-cache", "a.mo"] { Command.check files opts => files == ["a.mo"] && opts.no_cache == true && opts.workspace == false && opts.verbose == false && opts.affine == false, _ => false, }#[test]def test_from_args_check_no_cache_flag_combines_with_workspace : Bool := match Command.from_args ["check", "-w", "--no-cache", "a.mo", "b.mo"] { Command.check files opts => files == ["a.mo", "b.mo"] && opts.workspace == true && opts.no_cache == true && opts.affine == false, _ => false, }#[test]def test_from_args_check_without_no_cache_is_false : Bool := match Command.from_args ["check", "a.mo"] { Command.check files opts => files == ["a.mo"] && opts.no_cache == false && opts.affine == false, _ => false, }// `--affine` must be PEELED (not handed to the path expander as a// filename) and must set its flag, mirroring the `--workspace` and// `--no-cache` peel tests just above.#[test]def test_from_args_check_affine_flag : Bool := match Command.from_args ["check", "--affine", "a.mo"] { Command.check files opts => files == ["a.mo"] && opts.affine == true && opts.workspace == false && opts.verbose == false && opts.no_cache == false, _ => false, }// `Command.test` carries `jobs` as its fourth field, so every match here// binds it positionally -- adding a field to the constructor and leaving// these at three binders compiles clean and then fails at runtime, the// exact hazard AGENTS.md records for positional struct matches (this file// shipped that way once: run 38147433273, five red tests).#[test]def test_from_args_test : Bool := match Command.from_args ["test", "a.mo", "b.mo", "-v"] { Command.test files verbose workspace jobs => files == ["a.mo", "b.mo"] && verbose == true && workspace == false && jobs == 4, _ => false, }#[test]def test_from_args_test_workspace_flag : Bool := match Command.from_args ["test", "--workspace"] { Command.test files verbose workspace jobs => List.is_empty files && workspace == true && verbose == false && jobs == 4, _ => false, }#[test]def test_from_args_test_workspace_short_flag : Bool := match Command.from_args ["test", "-w", "-v"] { Command.test files verbose workspace jobs => List.is_empty files && workspace == true && verbose == true && jobs == 4, _ => false, }// The flag must be PEELED, not left among the paths -- otherwise it is// handed to the path expander as a filename.#[test]def test_from_args_test_workspace_flag_not_a_path : Bool := match Command.from_args ["test", "--workspace", "a.mo"] { Command.test files verbose workspace jobs => files == ["a.mo"] && workspace == true, _ => false, }// `--jobs/-j <N>`: peeled with the other flags, its number not left among// the paths, and its value carried into the command.#[test]def test_from_args_test_jobs_flag : Bool := match Command.from_args ["test", "--jobs", "2", "a.mo"] { Command.test files verbose workspace jobs => files == ["a.mo"] && jobs == 2, _ => false, }#[test]def test_from_args_test_jobs_short_flag_clamps_below_one : Bool := match Command.from_args ["test", "-j", "0", "a.mo"] { Command.test files verbose workspace jobs => files == ["a.mo"] && jobs == 1, _ => false, }// A bare `monad test` no longer means "print help": it means "test the// mote containing the working directory" (and only falls back, at RUN// time and with exit 1, to `no_target_diagnostic` when there is no such// mote).#[test]def test_from_args_test_no_files_is_a_test_command : Bool := match Command.from_args ["test"] { Command.test files verbose workspace jobs => List.is_empty files && workspace == false && verbose == false, _ => false, }#[test]def test_from_args_help_on_empty : Bool := match Command.from_args [] { Command.help => true, _ => false, }#[test]def test_from_args_help_on_unknown : Bool := match Command.from_args ["bogus"] { Command.help => true, _ => false, }// ─── the IR path ───//// Not argv parsing, but the other pure rule in `main.mo`, and the one the// artifact cache's correctness rests on: the file `llc` is pointed at must// be named by the cache key rather than by the output, because `llc`// records that name (its basename) in the object it emits, and an object// that records the output name is an object that differs between two// builds of one unchanged source. `Build.artifact_ir_path` supplies the// keyed path; `resolve_ir_path` is what lets every caller with no key to// name one by keep the output-derived path it always had./// A key is a key: the IR follows it and nothing else. There is no/// output-name parameter to pass, and that is the fix stated as a type.#[test]def test_resolve_ir_path_prefers_the_keyed_path : Bool := String.beq (Path.to_string (resolve_ir_path (Option.some (Path.path "target/store/abc.ll")) (Path.path "out") (Path.path "hello"))) "target/store/abc.ll"/// The no-key fallback: `run`, the test driver, and a build whose/// compiler digest could not be taken. Unchanged behaviour for all of/// them, which is why they did not have to change.#[test]def test_resolve_ir_path_falls_back_to_the_output : Bool := String.beq (Path.to_string (resolve_ir_path Option.none (Path.path "out") (Path.path "hello"))) "out/hello.ll"/// ...and the fallback still honours `Path.join`'s absolute-name rule,/// which is what every ladder script's absolute `-o` relies on.#[test]def test_resolve_ir_path_fallback_replaces_an_absolute_output : Bool := String.beq (Path.to_string (resolve_ir_path Option.none (Path.path "out") (Path.path "/tmp/x/hello"))) "/tmp/x/hello.ll"