Skip to content

Conjoin two ungraded error types into their sum - #385

Merged
Bronek merged 1 commit into
mainfrom
bronek/conjoin_fix
Jul 25, 2026
Merged

Conjoin two ungraded error types into their sum#385
Bronek merged 1 commit into
mainfrom
bronek/conjoin_fix

Conversation

@Bronek

@Bronek Bronek commented Jul 25, 2026

Copy link
Copy Markdown
Member

Fixes #384.

expected<int, Error> & expected<bool, ErrorOther> was ill-formed unless one of the two error types was already a copack, while the same pair disjoined without complaint. The conjunction's error channel is the sum, so producing the grade is the operator's work, not a precondition on its operands — and nowhere else does a sum ask for one: | forms its value sum out of two ungraded value types, in every carrier. Of the four channels — &'s value product and error sum, |'s value sum and error product — this was the only one gated.

The fix deletes the clause

&& (some_copack<typename ::std::remove_cvref_t<Lh>::error_type>
    || some_copack<typename ::std::remove_cvref_t<Rh>::error_type>)

from the four different-error operator& arms (void×value, value×void, void×void, value×value). Each already computes copack_for<El, Er> and lifts every operand's error through the copack<>-guarded _nothrow_arm, so nothing downstream moved: the widened result, the injection by type and the computed noexcept are those the graded pair has always had. fn::conjoin follows without a change of its own, being constrained on the fold being well-formed.

Purely additive — every expression newly accepted was ill-formed before, and a matching pair of error types still collapses to the plain error it always had. In the n-ary fold only the first pair was ever blocked: conjoin folds left, so after one & the accumulated error is a copack and every later step already took the graded path.

The bind is deliberately untouched. and_then and or_else are the foundational operations, and a callback whose error type merely differs from self's still refuses: switching a pipeline to the graded monad is the caller's decision, not something a bind makes for them.

Tests

tests/fn/expected.cpp carried the defect as a pin — static_assert(not can_amp(fn::expected<int, other_error>{1})); // mismatched non-graded error — now positive, with a converse keeping the kinds apart (not can_amp(fn::optional<int>{1})). A new SECTION("different plain error types") covers all four value shapes: the produced types, the leftmost-failure injection by type through a single two-arm probe (so both arms run), the constant-evaluated twins, and the noexcept axis where both operands' error relocations weigh. tests/fn/pack.cpp gains the matching conjoin rows beside the existing carrier fold.

Verification

  • six-config gate — gcc and clang × Debug, Release, VALIDATE_CXX23 — 6/6 pass
  • C++26 mode lane (LIBFN_CXX26) — pass, 86/86
  • clang-format --dry-run -Werror clean

The conjunction's error channel is the sum, so producing the grade is the
operator's work, not a precondition on its operands - and nowhere else does
a sum ask for one: `|` forms its value sum out of two ungraded value types.
The four different-error arms drop the clause requiring one operand's error
to be a copack already; each already widens to copack_for<E1, E2>.

Assisted-by: Claude:claude-opus-5
@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@augmentcode

augmentcode Bot commented Jul 25, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: This PR fixes a constraint bug in fn::expected conjunction so that operator& can conjoin two ungraded, mismatched error types.

Changes:

  • Removed the extra some_copack precondition from the four “different error type” expected operator& overloads, letting the operator form the error sum itself.
  • Added/updated unit tests to assert that conjoining expected<T, E1> with expected<U, E2> yields expected<... , copack_for<E1, E2>> across all value-shape combinations.
  • Extended conjoin fold tests to cover mismatched plain error types and verify correct error injection/leftmost-failure behavior and noexcept properties.

Technical Notes: The change is intentionally additive (previously ill-formed expressions become well-formed) while preserving the matching-error fast path (still collapses to the plain error type).

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. No suggestions at this time.

Comment augment review to trigger a new review at any time.

@sonarqubecloud

Copy link
Copy Markdown

@Bronek
Bronek merged commit 0554d29 into main Jul 25, 2026
66 checks passed
Bronek added a commit that referenced this pull request Jul 26, 2026
Sections 1, 2, 6, 7 and 9 no longer state the grading precondition #385
removed; section 6's example now conjoins plain error types, so the fence
asserts the operator forming the grade rather than requiring it. Sections 6
and 7 share one skeleton, each stating its channel rules once.

The rest is prose: announcing openers, passages restating their own section,
and three admonitions.

Assisted-by: Claude:claude-opus-5
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.

operator& requires an already-graded error to conjoin different error types

1 participant