Repository navigation
CMTK0003 misses async assertions inside rethrowing catches #166
Description
Problem
CMTK0003 treats every catch surrounding an awaited assertion in an async lambda as if it observed the failure. A catch that unconditionally rethrows, cannot catch the assertion exception, or has an always-false filter does not observe it; the outer dropped task remains the only place that failure is visible.
Reproduced at release tip 9878a6cb7500dbf7d3e1a199c3b9446d7e5cc278 with the real analyzer and Testing assemblies through Roslyn Compilation.WithAnalyzers, without compiler errors:
public static void Check(Task<Option<int>> pending)
{
Task.WhenAll(new[] { pending }.Select(async item =>
{
try { await item.ShouldBeNone(); }
catch { throw; }
}));
}No CMTK0003 is produced. Replacing the catch with catch (OperationCanceledException) { } or catch (Exception) when (false) { } also gives no diagnostic. All three leave a TypeKitAssertionException unobserved when the pending value is Some. A runtime check confirmed the rethrowing lambda faults its outer WhenAll task with TypeKitAssertionException; dropping that task lets the test pass.
Controls: a directly awaited assertion in a lambda without a catch (including a try/finally) is diagnosed when the outer WhenAll task is dropped. A catch that actually swallows the failure is correctly silent.
Cause
IgnoredOutcomeAnalyzer.EnclosingAsyncFunction immediately returns null for any ITryOperation with a nonempty Catches list, without considering whether those catches handle the failure or rethrow it. HandsOn therefore loses the assertion's failure path before it reaches the async lambda and task combinator.
Expected
Do not treat a rethrow-only catch or a catch that cannot handle the assertion's failure as observing it. Extend the async-lambda axis of DroppedTaskShapes with no catch, try/finally, swallowing catch, rethrow/log-and-rethrow, unrelated exception type and filtered catch controls. Keep awaited/held/returned/explicit-discard uses silent where appropriate.
Found in the second pre-release review, in the async-lambda reach added with #157/#164. Decide and fix before the tag under decision 119.
Activity
CaffeinatedCoder commentedon Oct 29, 2026
Fixed in #172 (into
release/2.3.0, commit f636a06). A catch now stops the failure only when theawaitis in itstryblock (not a handler orfinally), its type isTypeKitAssertionExceptionor a base of it (resolved in the Testing assembly; a look-alike of that name elsewhere doesn't count), and its handler can complete by Roslyn control flow (end point reachable, or areturn/breakleaves it).catch { throw; }, log-and-throw;,throw new …,catch (OperationCanceledException)andwhen (false)(handler unreachable) now report. Decision: a filter that is no constant (when (Expected(e))) counts as keeping the failure, since an Error reports only a failure lost whatever the filter answers; written indesign/analyzers.mdand the analyzer README. DroppedTaskShapes gained every catch kind and the silent controls (catch all, catch the assertion's exception, true/opaque filter, conditional rethrow, return, a later clause, nested tries).CaffeinatedCoder commentedon Oct 29, 2026
Fixed by #172, merged into
release/2.3.0.