From 9784ff870545fb2c237d0f4ea271341e5e84664d Mon Sep 17 00:00:00 2001 From: agra Date: Thu, 4 Jun 2026 07:54:20 +0300 Subject: [PATCH] F3.2: assert Diag for the zero-arg and too-many-flags raise sites Example 0717 now asserts the (token, index) Diag for ALL SIX raise sites in cli.sx, closing the two the reviewer found still unasserted: - zero-arg UnknownCommand: parse([], ...) -> index -1, token "" (the args.len == 0 sub-branch of cli.sx:237, distinct from the one-arg too-few form already covered at index 0 / token args[0]). - TooManyFlags (cli.sx:256): a command declaring 17 flag specs (> the inline 16 cap) is rejected, not truncated -> index -1, token command. The three index==-1 cases (zero-arg, too-many, missing-req) seed their Diag with a sentinel before parse, so each assertion proves parse WROTE the -1/"" rather than merely matching the `.{}` default. Verified non-vacuous: flipping any expected value makes that line FAIL. Test-only: cli.sx logic and src/ are untouched. --- examples/0717-modules-cli-parse.sx | 79 +++++++++++++++---- .../expected/0717-modules-cli-parse.stdout | 6 ++ 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/examples/0717-modules-cli-parse.sx b/examples/0717-modules-cli-parse.sx index e21e416..033f8a7 100644 --- a/examples/0717-modules-cli-parse.sx +++ b/examples/0717-modules-cli-parse.sx @@ -106,6 +106,7 @@ main :: () -> ! { report("dash-value", dpv.value_of("out") == "-5" and dpv.is_set("verbose")); // ── 7. Failures: each surfaces the specific variant ────────────── + a_zero_args : []string = .[]; // nothing at all a_unknown_cmd : []string = .["ci", "deploy", "--out", "x"]; a_unknown_group : []string = .["zz", "publish", "--out", "x"]; a_too_few : []string = .["ci"]; @@ -113,22 +114,60 @@ main :: () -> ! { a_missing_value : []string = .["ci", "publish", "--out"]; a_value_eats : []string = .["ci", "publish", "--out", "--verbose"]; a_missing_req : []string = .["ci", "publish", "--verbose"]; - report("err-unknown-cmd", raises(a_unknown_cmd, cmds, error.UnknownCommand)); - report("err-unknown-group", raises(a_unknown_group, cmds, error.UnknownCommand)); - report("err-too-few", raises(a_too_few, cmds, error.UnknownCommand)); - report("err-unknown-flag", raises(a_unknown_flag, cmds, error.UnknownFlag)); - report("err-missing-value", raises(a_missing_value, cmds, error.MissingValue)); - report("err-value-eats-flag", raises(a_value_eats, cmds, error.MissingValue)); - report("err-missing-req", raises(a_missing_req, cmds, error.MissingRequired)); - // ── 8. Diag pins the offending (token, index) for EVERY error case ─ + // A command whose FlagSpec list exceeds the inline `Parsed.values` cap + // (16): the parser rejects it with TooManyFlags rather than silently + // truncating. 17 specs (> 16) trips the check right after dispatch + // matches (group, command), before any flag is read. + over_flags : []FlagSpec = .[ + FlagSpec.{ name = "f00", takes_value = false, required = false }, + FlagSpec.{ name = "f01", takes_value = false, required = false }, + FlagSpec.{ name = "f02", takes_value = false, required = false }, + FlagSpec.{ name = "f03", takes_value = false, required = false }, + FlagSpec.{ name = "f04", takes_value = false, required = false }, + FlagSpec.{ name = "f05", takes_value = false, required = false }, + FlagSpec.{ name = "f06", takes_value = false, required = false }, + FlagSpec.{ name = "f07", takes_value = false, required = false }, + FlagSpec.{ name = "f08", takes_value = false, required = false }, + FlagSpec.{ name = "f09", takes_value = false, required = false }, + FlagSpec.{ name = "f10", takes_value = false, required = false }, + FlagSpec.{ name = "f11", takes_value = false, required = false }, + FlagSpec.{ name = "f12", takes_value = false, required = false }, + FlagSpec.{ name = "f13", takes_value = false, required = false }, + FlagSpec.{ name = "f14", takes_value = false, required = false }, + FlagSpec.{ name = "f15", takes_value = false, required = false }, + FlagSpec.{ name = "f16", takes_value = false, required = false }, + ]; + over_cmds : []Command = .[ Command.{ group = "big", command = "cmd", flags = over_flags } ]; + over_args : []string = .["big", "cmd"]; + + report("err-zero-args", raises(a_zero_args, cmds, error.UnknownCommand)); + report("err-unknown-cmd", raises(a_unknown_cmd, cmds, error.UnknownCommand)); + report("err-unknown-group", raises(a_unknown_group, cmds, error.UnknownCommand)); + report("err-too-few", raises(a_too_few, cmds, error.UnknownCommand)); + report("err-unknown-flag", raises(a_unknown_flag, cmds, error.UnknownFlag)); + report("err-missing-value", raises(a_missing_value, cmds, error.MissingValue)); + report("err-value-eats-flag", raises(a_value_eats, cmds, error.MissingValue)); + report("err-missing-req", raises(a_missing_req, cmds, error.MissingRequired)); + report("err-too-many-flags", raises(over_args, over_cmds, error.TooManyFlags)); + + // ── 8. Diag pins the offending (token, index) for EVERY raise site ─ // Each failure records the exact offending token (a VIEW into `args`, - // except a missing-required names the matched spec's flag name) plus - // its `args` index, so a caller can report which token failed. An - // unknown (group, command) PAIR — group wrong OR command wrong — pins - // the COMMAND token at index 1, since the pair as a whole is what - // failed to match; a missing-required uses index -1 (the token is a - // flag name, not an `args` slot). + // except missing-required / too-many which name the spec's flag / the + // command) plus its `args` index, so a caller can report which token + // failed. This covers ALL SIX raise sites in cli.sx, both UnknownCommand + // sub-branches included: + // - zero-arg -> index -1, token "" (args.len == 0) + // - too-few -> index 0, token args[0] (args.len == 1) + // - unknown pair -> index 1, token command (group OR command wrong) + // - too-many -> index -1, token command (spec count > 16 cap) + // - unknown flag -> index i, token flag tok + // - missing val -> index i, token flag tok + // - missing req -> index -1, token flag name + // The three index==-1 cases (zero-arg, too-many, missing-req) COINCIDE + // with `Diag`'s `.{}` defaults (index -1, token ""), so those Diags are + // seeded with a sentinel first: the assertion then proves `parse` + // actually WROTE the value, not that it merely left the default. de : Diag = .{}; _, ue := parse(a_unknown_flag, cmds, @de); report("diag-flag-tag", ue == error.UnknownFlag); @@ -149,6 +188,11 @@ main :: () -> ! { report("diag-too-few-tag", fe == error.UnknownCommand); report("diag-too-few-token", df.token == "ci" and df.index == 0); + d0 : Diag = .{ index = 999, token = "" }; // sentinel: -1/"" are defaults + _, z0e := parse(a_zero_args, cmds, @d0); + report("diag-zero-args-tag", z0e == error.UnknownCommand); + report("diag-zero-args-token", d0.token == "" and d0.index == -1); + dv : Diag = .{}; _, ve := parse(a_missing_value, cmds, @dv); report("diag-missing-value-tag", ve == error.MissingValue); @@ -159,12 +203,17 @@ main :: () -> ! { report("diag-value-eats-tag", ze == error.MissingValue); report("diag-value-eats-token", dz.token == "--out" and dz.index == 2); - dm : Diag = .{}; + dm : Diag = .{ index = 999, token = "" }; // sentinel: -1 is the default _, me := parse(a_missing_req, cmds, @dm); report("diag-req-tag", me == error.MissingRequired); report("diag-req-token", dm.token == "out"); report("diag-req-index", dm.index == -1); + dt : Diag = .{ index = 999, token = "" }; // sentinel: -1 is the default + _, te := parse(over_args, over_cmds, @dt); + report("diag-too-many-tag", te == error.TooManyFlags); + report("diag-too-many-token", dt.token == "cmd" and dt.index == -1); + print("=== DONE ===\n"); return; } diff --git a/examples/expected/0717-modules-cli-parse.stdout b/examples/expected/0717-modules-cli-parse.stdout index c658a4c..74b2bd2 100644 --- a/examples/expected/0717-modules-cli-parse.stdout +++ b/examples/expected/0717-modules-cli-parse.stdout @@ -19,6 +19,7 @@ sep-no-bool: ok bare-rest-len: ok bare-rest-0: ok dash-value: ok +err-zero-args: ok err-unknown-cmd: ok err-unknown-group: ok err-too-few: ok @@ -26,6 +27,7 @@ err-unknown-flag: ok err-missing-value: ok err-value-eats-flag: ok err-missing-req: ok +err-too-many-flags: ok diag-flag-tag: ok diag-flag-token: ok diag-cmd-tag: ok @@ -34,6 +36,8 @@ diag-group-tag: ok diag-group-token: ok diag-too-few-tag: ok diag-too-few-token: ok +diag-zero-args-tag: ok +diag-zero-args-token: ok diag-missing-value-tag: ok diag-missing-value-token: ok diag-value-eats-tag: ok @@ -41,4 +45,6 @@ diag-value-eats-token: ok diag-req-tag: ok diag-req-token: ok diag-req-index: ok +diag-too-many-tag: ok +diag-too-many-token: ok === DONE ===