Skip to content

Add Outcome.Try with usage analyzers FCE019–FCE022 - #265

Merged
Reefact merged 3 commits into
mainfrom
claude/outcome-try-feature-z1u1n4
Jul 21, 2026
Merged

Add Outcome.Try with usage analyzers FCE019–FCE022#265
Reefact merged 3 commits into
mainfrom
claude/outcome-try-feature-z1u1n4

Conversation

@Reefact

@Reefact Reefact commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Summary

Add Outcome.Try, the single sanctioned bridge from throwing code into the outcome flow: it runs an operation, catches one caller-named exception type, and maps it to an error through a mandatory mapper, while always letting cancellation propagate. Four usage analyzers (FCE019–FCE022) guard its intended use at build time, and the design is recorded in ADR-0028 (guarded bridge) and ADR-0029 (async-surface completion).

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring
  • Analyzer / diagnostic change
  • Tests
  • Documentation
  • Build / CI / tooling

Changes

  • Add Outcome.Try with six overloads on netstandard2.0: value and side-effecting forms, each synchronous, asynchronous with a CancellationToken, and token-less asynchronous. Each catches a single caller-named exception type, requires an explicit error mapper, and lets OperationCanceledException propagate.
    • The token-less Func<Task> / Func<Task<T>> overloads (mirroring the BCL's Task.Run set) ensure an async lambda binds to an awaited Task-returning delegate instead of the synchronous Action as async void — closing a silent-crash footgun structurally rather than by detection.
    • A null operation result and a null returned task are treated as contract violations that surface (rather than being mapped) even under a broad TException; the guards sit outside the exception-mapping region.
  • Add four usage analyzers, shipped in the package:
    • FCE019 TryCatchesTooBroadly (Warning, on) — Try catches exactly System.Exception.
    • FCE020 TryCatchesRichProtocolException (Warning, opt-in) — Try catches an HTTP / socket / DB protocol exception; advisory prompt to consider a dedicated result-inspecting adapter.
    • FCE021 PreferNonThrowingAlternativeToTry (Warning, on) — Try wraps a call with a framework-available non-throwing TryXxx / TryCreate counterpart (advisory, framework-aware).
    • FCE022 TryCatchesCancellation (Warning, on) — Try binds the caught type to OperationCanceledException (or a subtype), a provably dead catch.
  • Document the feature (Outcome guide + one rule page per analyzer, English canonical with French translations) and record ADR-0028 (guarded bridge) and ADR-0029 (token-less async overloads, refining ADR-0028's API-surface wording).

Testing

  • dotnet build FirstClassErrors.sln
  • dotnet test FirstClassErrors.sln
  • Analyzer tests pass (FirstClassErrors.Analyzers.UnitTests)

Full solution build: 0 warnings, 0 errors. dotnet test FirstClassErrors.sln: all projects passing (core 447, analyzers 132, and the rest), including the async-lambda and fire-and-forget regressions, the null-task/null-result contract, and tests compiling snippets against the .NET Framework 4.7.2 reference set for FCE021's framework-awareness. Re-verified after rebasing onto the latest main.

Documentation

  • Public API / error documentation updated
  • README / doc/ updated
  • French translation updated (user-facing behavior changed)
  • No documentation change required

French user-facing docs are kept in lockstep with the English canonical (Outcome guide, the four analyzer rule pages, the analyzer index) and both ADRs have French translations.

Architecture decisions

  • No architectural decision in this pull request
  • New decision recorded — ADR-0028 (guarded bridge) and ADR-0029 (async-surface completion), both Accepted
  • Supersedes an existing ADR — successor proposed, status not flipped: ADR-____
  • ⚠️ Conflicts with an existing ADR — flagged for the maintainer: ADR-____

Notes:

  • Both ADRs were accepted by the maintainer. ADR-0029 refines ADR-0028's "rather than broadening the API" clause (scoped to the semantic misuses FCE019–FCE022) to allow completing the async surface, since the async-void binding is type-visible, has no legitimate use, and is fixed by adding an overload.
  • These were originally numbered ADR-0027/ADR-0028; they were renumbered to 0028/0029 after main merged its own ADR-0027 ("Repair Dependabot pull requests within a risk boundary"). Numbers only — no decision or content changed.

Related issues

Refs #267 — the async-void footgun; resolved structurally in this PR by the token-less overloads. The issue is left open, repurposed to track an optional defence-in-depth analyzer for the residual non-accidental cases (an explicitly Action-typed variable, an async void method group passed by name).

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e4730a698

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread FirstClassErrors/Outcome.cs Outdated
Comment thread FirstClassErrors/Outcome.cs
Reefact pushed a commit that referenced this pull request Jul 21, 2026
Outcome.Try's value overloads called Outcome<T>.Success(operation())
inside the guarded try, so when the operation returned null, Success's own
null-guard threw ArgumentNullException inside the catch region. Under a
broad TException (Exception, or ArgumentNullException) onError then mapped
that library guard as an anticipated failure, hiding a contract violation.

Move Success(result) after the catch in both the synchronous and
asynchronous value overloads so a null result surfaces as the
ArgumentNullException it is. The operation call and EnsureTask stay inside
the try, so a synchronous throw and a null task are still caught as before;
the void overloads use the argument-less Success property and are
unaffected. Cover both overloads with a test and document the contract in
their <exception> summaries.

Addresses a Codex review finding on PR #265.
@Reefact

Reefact commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

@codex Please perform an in-depth review of the code introduced or modified by this pull request, which implements a new feature.

The objective is to determine whether the feature is correctly designed, correctly implemented, sufficiently tested, and consistently integrated into the existing project.

Review scope

Start by reading:

  1. the pull request description;
  2. any referenced issues, specifications, or ADRs;
  3. the relevant functional and technical documentation;
  4. the repository conventions, including CLAUDE.md, AGENTS.md, contribution guides, and applicable architectural rules;
  5. the complete pull request diff;
  6. any existing code required to understand how the feature integrates with the rest of the system.

Do not review the diff in isolation. Verify that the changes are consistent with the abstractions, behaviors, contracts, and conventions already established in the project.

Review areas

1. Requirements and expected behavior

Verify that the implementation actually satisfies the described requirements.

Look specifically for:

  • missing or only partially implemented requirements;
  • behaviors introduced without being specified;
  • ambiguities resolved in a questionable way;
  • unhandled edge cases;
  • discrepancies between the expected behavior, documentation, tests, and implementation.

Do not treat a behavior as incorrect merely because a different design would also have been possible. Report an issue only when there is a demonstrable contradiction, omission, defect, or concrete risk.

2. Design and architecture

Evaluate:

  • consistency with the existing architecture;
  • separation of responsibilities;
  • newly introduced dependencies;
  • coupling and cohesion;
  • the quality and stability of public abstractions;
  • enforcement of domain invariants;
  • error handling;
  • consistency with existing patterns;
  • meaningful duplication;
  • premature abstractions;
  • classes or components carrying too many responsibilities.

Also verify that the feature does not introduce a second competing way to achieve an existing operation without an explicit and justified reason.

3. Public API and developer experience

For every public API, fluent API, extension method, overload, or generic abstraction added or modified, review:

  • naming clarity;
  • consistency with existing APIs;
  • generic type inference;
  • generic constraints;
  • nullability;
  • overload resolution and ambiguity risks;
  • compilation failures for otherwise legitimate usages;
  • IntelliSense discoverability;
  • error-message quality;
  • source and binary compatibility;
  • surprising or difficult-to-predict behavior.

Mentally validate several realistic usage scenarios, including edge cases and invalid usages.

4. Code correctness

Look for concrete defects, including:

  • logical errors;
  • incorrect conditional branches;
  • incorrectly propagated values;
  • incomplete null handling;
  • unexpected exceptions;
  • type conversion, variance, or generic inference issues;
  • incorrect behavior for empty collections;
  • execution-order problems;
  • uncontrolled mutable state;
  • unintended non-deterministic behavior;
  • invalid assumptions about inputs;
  • concurrency or thread-safety issues, where relevant;
  • significant performance or allocation problems.

Do not report micro-optimizations without a realistic impact.

5. Tests

Assess whether the tests genuinely demonstrate the intended behavior of the feature.

Verify coverage of:

  • nominal scenarios;
  • expected failures;
  • edge cases;
  • likely regressions;
  • null values and empty collections;
  • relevant generic variants;
  • deterministic behavior;
  • interactions between new components and existing code;
  • backward-compatibility guarantees.

Identify tests that may pass without actually proving the announced behavior, as well as tests that are excessively coupled to implementation details.

Do not mechanically request a test for every line or method. Recommend tests only when they protect an important behavior or an identified risk.

6. Documentation

Verify that:

  • the documentation describes the behavior actually implemented;
  • examples are conceptually compilable and use valid APIs;
  • signatures and names are up to date;
  • French and English documentation remain consistent when both are affected;
  • important limitations and design choices are documented;
  • no misleading, obsolete, or technically incorrect statement has been introduced.

Do not criticize writing style unless it creates a real technical ambiguity or communicates incorrect behavior.

7. Regressions and integration

Analyze the impact on existing code, including:

  • source or binary compatibility breaks;
  • unintended changes to existing behavior;
  • semantic changes to an existing API;
  • impact on downstream consumers;
  • interactions with analyzers, generators, renderers, publishers, or other affected modules;
  • newly introduced compiler warnings;
  • nullability regressions;
  • performance or memory consequences;
  • inconsistencies between packages or modules.

Do not perform a general audit of the entire repository. Inspect existing code only when it is necessary to evaluate the changes introduced by this pull request.

Expected output

Start with a concise summary containing:

  • the overall assessment;

  • the confidence level in the implementation;

  • the main identified risks, if any;

  • an explicit conclusion using one of the following:

    • ready to merge;
    • ready after minor corrections;
    • significant corrections required.

Then report only genuinely actionable findings, classified using these severity levels:

  • Blocking: bug, contract violation, security issue, data loss, confirmed regression, or substantially incorrect functionality;
  • Important: a real issue that should be corrected before merge but does not make the entire feature unusable;
  • Useful improvement: a justified improvement that is not required before merge.

For each finding, include:

  1. the affected file and lines;
  2. the current behavior;
  3. the concrete problem;
  4. a scenario demonstrating or explaining the risk;
  5. the recommended correction;
  6. the test that should protect the correction, when relevant.

Group findings that share the same root cause instead of reporting multiple variations of the same issue.

After the findings, add a separate section for open questions. Do not present a question as a defect unless the answer establishes that an actual issue exists.

Finish with:

  • aspects that were particularly well implemented;
  • requirements that were correctly implemented and covered;
  • residual risks that cannot be confirmed through static code review alone.

Review rules

  • Do not invent problems merely to produce a longer review.
  • Do not recommend a complete rewrite when a local correction is sufficient.
  • Do not criticize stylistic choices already accepted by the repository.
  • Do not request refactoring without a concrete benefit for this feature.
  • Do not present personal preferences as project requirements.
  • Do not report hypothetical risks without explaining precisely how they could occur.
  • Verify every finding before reporting it.
  • Prefer a small number of strong, actionable findings over a long list of minor remarks.
  • Do not modify any files. This task is strictly a code review and analysis of the pull request.
  • Use the highest available reasoning depth.
  • Do not conclude before reviewing the complete diff and the main execution paths affected by the feature.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4442dc7006

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread FirstClassErrors/Outcome.cs
Reefact pushed a commit that referenced this pull request Jul 21, 2026
Outcome.Try's value overloads called Outcome<T>.Success(operation())
inside the guarded try, so when the operation returned null, Success's own
null-guard threw ArgumentNullException inside the catch region. Under a
broad TException (Exception, or ArgumentNullException) onError then mapped
that library guard as an anticipated failure, hiding a contract violation.

Move Success(result) after the catch in both the synchronous and
asynchronous value overloads so a null result surfaces as the
ArgumentNullException it is. The operation call and EnsureTask stay inside
the try, so a synchronous throw and a null task are still caught as before;
the void overloads use the argument-less Success property and are
unaffected. Cover both overloads with a test and document the contract in
their <exception> summaries.

Addresses a Codex review finding on PR #265.
@Reefact
Reefact force-pushed the claude/outcome-try-feature-z1u1n4 branch from 6668405 to c788911 Compare July 21, 2026 15:17
claude added 3 commits July 21, 2026 15:23
Add Outcome.Try, the single sanctioned way to run a throwing operation and
capture its throw as a failed Outcome through a mandatory error mapper — the
inverse of GetResultOrThrow / ThrowIfFailure / ToException.

Six overloads on netstandard2.0: value and side-effecting forms, each
synchronous, asynchronous with a CancellationToken, and token-less
asynchronous. Each catches a single caller-named exception type and lets
OperationCanceledException propagate. The token-less Func<Task> / Func<Task<T>>
overloads mirror the BCL's Task.Run set so an async lambda binds to an awaited
Task-returning delegate instead of the synchronous Action as async void. A null
result and a null returned task are contract violations that surface rather than
being mapped, even under a broad TException.

Document the entry/exit of the outcome flow in the Outcome guide (EN + FR).

Refs: #267
Ship four usage analyzers in the package that guard Outcome.Try's narrow
intended use:

- FCE019 TryCatchesTooBroadly (Warning, on): catches exactly System.Exception.
- FCE020 TryCatchesRichProtocolException (Warning, opt-in): catches an HTTP /
  socket / DB protocol exception; advisory prompt for a dedicated adapter.
- FCE021 PreferNonThrowingAlternativeToTry (Warning, on): wraps a call that has
  a framework-available non-throwing TryXxx / TryCreate counterpart (advisory,
  framework-aware).
- FCE022 TryCatchesCancellation (Warning, on): binds the caught type to
  OperationCanceledException or a subtype, a provably dead catch.

Cover them with tests, including snippets compiled against the .NET Framework
4.7.2 reference set to prove FCE021's framework-awareness, and bilingual rule
pages plus the analyzer index.
ADR-0028 records the decision to provide Outcome.Try as the guarded bridge
into the outcome flow (mandatory mapper, single caught type, cancellation
propagates, analyzer-guarded). ADR-0029 records completing the async surface
with token-less Func<Task> / Func<Task<T>> overloads, refining ADR-0028's
"rather than broadening the API" clause for the type-visible async-void case.

Both accepted; numbered above main's ADR-0027 (Dependabot). English canonical
with French translations, indexed.
@Reefact
Reefact force-pushed the claude/outcome-try-feature-z1u1n4 branch from c788911 to 4971be0 Compare July 21, 2026 15:23
@Reefact
Reefact merged commit 7c5075b into main Jul 21, 2026
15 checks passed
@Reefact
Reefact deleted the claude/outcome-try-feature-z1u1n4 branch July 21, 2026 15:29
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.

2 participants