Skip to content

refactor(lib)!: mark ParseOutput and UsageErr non_exhaustive - #772

Open
JamBalaya56562 wants to merge 1 commit into
jdx:mainfrom
JamBalaya56562:non-exhaustive-parse-output
Open

refactor(lib)!: mark ParseOutput and UsageErr non_exhaustive#772
JamBalaya56562 wants to merge 1 commit into
jdx:mainfrom
JamBalaya56562:non-exhaustive-parse-output

Conversation

@JamBalaya56562

@JamBalaya56562 JamBalaya56562 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Two attributes, and a repair for the version signal #762 lost.

The consistency part

Ten public types in usage-lib already carry #[non_exhaustive]Spec, SpecArg, SpecFlag, SpecCommand's SpecExample, Parser, and others. ParseOutput and UsageErr were missed, and #762 ran straight into the consequence: adding next_arg and double_dash_seen to one, and ArgRequiresDoubleDash to the other, were each reported as breaking on their own.

Neither is a type a caller builds or matches exhaustively:

  • ParseOutput is what parse() returns. Its only struct literal is in parse.rs itself; nothing outside the crate constructs one.
  • UsageErr is an error type, where a wildcard arm is the normal shape. Nothing in the workspace matches on it — usage-cli uses it as a return type and downcasts once, in cli/src/lib.rs.

cargo build --all --all-features passes untouched, which is the workspace-level check that neither pattern exists.

The repair part

main has been red since #762 merged, and this fixes the cause.

The squash took its subject from the PR title, which had no !, so the BREAKING CHANGE: footer on the branch commit never reached main. What landed was:

fix(parse): enforce double_dash="required" for positional args (#762)

git-cliff therefore reads main as holding only a fix: since v4.1.0, and --bumped-version would compute v4.1.1 — shipping two breaking API changes as a patch. Every PR opened against main is red in the meantime, because lint:semver compares Cargo.toml (4.1.0) against crates.io (4.1.0), calls it no change; assume patch, and any major finding fails.

Marking a type #[non_exhaustive] is itself a breaking change, so the ! on this commit is the honest description of what it does, not a marker added to game the version. And it puts the major back within reach of --bumped-version, so the next release computes v5.0.0 and covers #762 as well.

What lint:semver says now

Still two failures, but different — and better ones:

--- failure enum_marked_non_exhaustive ---
  enum UsageErr in lib/src/error.rs:6

--- failure struct_marked_non_exhaustive ---
  struct ParseOutput in lib/src/parse.rs:242

Summary  semver requires new major version: 2 major and 0 minor checks failed

constructible_struct_adds_field and enum_variant_added are gone: once a struct cannot be constructed externally and an enum cannot be matched exhaustively, adding to either is no longer separately breaking. The attribute replaces two one-off findings with the durable statement, and that is the point of adding it now rather than later.

This PR does not turn lint:semver green. It goes green when the release PR sets Cargo.toml to 5.0.0. Merging this makes that release be 5.0.0.

Why the version is not bumped here

tasks/release-plz opens with

if ! echo "$released_versions" | grep -q "^v$cur_version$"; then
  cargo publish -p usage-lib
  ...
  git tag "v$cur_version"

which is the publish half of the release-PR flow. A hand-written bump in a feature PR is indistinguishable from a merged release branch, so it would publish to crates.io on the merge push — no CHANGELOG entry, no release PR, outside the cadence guard in auto-merge-release.yml. I tried exactly that on #762 and backed it out.

Verified

cargo build --all --all-features, cargo test --all --all-features, cargo fmt --all -- --check and cargo clippy --all --all-features --all-targets -- -D warnings all pass on Linux. cargo semver-checks check-release -p usage-lib fails with the two findings above, which is the intended outcome.

Three more types, if you want them

The same gap exists on SpecCommand (lib/src/spec/cmd.rs), ParseValue (lib/src/parse.rs) and SpecDoubleDashChoices (lib/src/spec/arg.rs). Since this release already spends a major, folding them in costs nothing extra — say the word and I will.

I left them out because one of them is not free. ParseValue is what consumers match to read a parsed value, so #[non_exhaustive] forces a wildcard arm on mise and anyone else doing the same. That is a judgement about downstream ergonomics rather than about semver hygiene, and it seemed yours to make. SpecCommand and SpecDoubleDashChoices carry no such cost.


This pull request was generated by Claude Code.

Summary by CodeRabbit

  • Refactor
    • Improved API forward compatibility by allowing additional usage error variants and parse output fields in future releases.
    • External integrations must avoid exhaustive matching or construction of these public types.

Ten public types in usage-lib already carry `#[non_exhaustive]` — `Spec`,
`SpecArg`, `SpecFlag`, `Parser` among them. These two were missed, and jdx#762 ran
into the consequence: adding a variant to `UsageErr` and two fields to
`ParseOutput` were each reported as breaking on their own.

Neither type is one a caller builds or matches exhaustively. `ParseOutput` is
what `parse()` returns; its only struct literal is in `parse.rs` itself, and
nothing outside the crate constructs one. `UsageErr` is an error type, where a
wildcard arm is the normal shape. Nothing in the workspace matches it — the CLI
uses it as a return type and downcasts once.

This also restores a signal that was lost. The squash of jdx#762 took its subject
from the PR title, which had no `!`, so the `BREAKING CHANGE:` footer on the
branch commit never reached main:

    fix(parse): enforce double_dash="required" for positional args (jdx#762)

git-cliff therefore reads main as holding only a `fix:` since v4.1.0 and would
compute v4.1.1 for the next release — shipping two breaking API changes as a
patch. Marking a type `#[non_exhaustive]` is itself a breaking change, so the
`!` here is the honest description of this commit, and it puts the major back
within reach of `--bumped-version`.

The version in Cargo.toml is deliberately untouched: `tasks/release-plz`
publishes and tags as soon as main carries a version with no matching tag, so a
hand-written bump in a feature PR would publish on the merge push with no
changelog entry and no release PR. `lint:semver` therefore stays red until the
release PR bumps it, which is the state main has been in since jdx#762 landed.

BREAKING CHANGE: `ParseOutput` can no longer be constructed with a struct
literal outside usage-lib, and `UsageErr` can no longer be matched exhaustively
outside it — a wildcard arm is now required. Both replace the narrower breaks
jdx#762 introduced with the durable form.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: b2551172-3955-4ec3-8dfc-3c2b03592227

📥 Commits

Reviewing files that changed from the base of the PR and between 6cbc931 and 48caedc.

📒 Files selected for processing (2)
  • lib/src/error.rs
  • lib/src/parse.rs

📝 Walkthrough

Walkthrough

The PR marks the public UsageErr enum and ParseOutput struct as #[non_exhaustive].

Changes

Public API updates

Layer / File(s) Summary
Mark public types non-exhaustive
lib/src/error.rs, lib/src/parse.rs
UsageErr and ParseOutput now use #[non_exhaustive].

Estimated code review effort: 1 (Trivial) | ~3 minutes

Possibly related PRs

  • jdx/usage#762: Updates the same public UsageErr enum and ParseOutput struct.

Suggested reviewers: jdx

Poem

A rabbit checks the public gate,
Two types now leave room for fate.
UsageErr hops, ParseOutput too,
Future fields can join the queue.
The API burrow stays safe and true.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the two public types marked non-exhaustive.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR marks two public usage-lib result types as non-exhaustive to make future additions forward-compatible and restore the intended major-version release signal.

  • Adds #[non_exhaustive] to the UsageErr public error enum.
  • Adds #[non_exhaustive] to the ParseOutput public parser-result struct.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete regressions identified in current workspace consumers.

The attributes intentionally tighten downstream construction and matching rules, while all current workspace usage remains compatible and runtime behavior is unchanged.

Important Files Changed

Filename Overview
lib/src/error.rs Marks UsageErr non-exhaustive; current workspace consumers do not exhaustively match the enum.
lib/src/parse.rs Marks ParseOutput non-exhaustive; its sole struct construction remains within the defining module and existing consumers use fields or methods.

Reviews (1): Last reviewed commit: "refactor(lib)!: mark ParseOutput and Usa..." | Re-trigger Greptile

@JamBalaya56562

Copy link
Copy Markdown
Contributor Author

CI is red on lint:semver only — everything else in the job passes, along with coverage, Socket, CodeRabbit and Greptile.

--- failure enum_marked_non_exhaustive ---  enum UsageErr
--- failure struct_marked_non_exhaustive --- struct ParseOutput
Summary  semver requires new major version

That is this PR's own change reported back. It cannot go green here: lint:semver compares Cargo.toml against crates.io, both are 4.1.0, so a deliberate breaking change fails by definition. Bumping the version in a feature PR is the one thing I will not do — as explained on #762, tasks/release-plz would publish to crates.io on the merge push.

main has been red the same way since #762 landed, because its squash took the subject from a PR title I forgot to mark !. That is what this PR repairs: with refactor(lib)!: on main, git cliff --bumped-version computes v5.0.0 instead of v4.1.1, and the release PR clears the red for both. #771 is red for the same reason and touches no library code at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant