You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A when guard on a C# switch arm is a real branch — the pattern can match
while the guard fails — and neither metric models it. C# cyclomatic has
no when_clause arm, and C# ABC has no guard rule. ABC nonetheless appears
to count some guards, because its token arms fire on whatever operator
happens to sit inside one. The result is that the score depends on how the
guard is spelled rather than on what it does.
Found while fixing #1383,
which deliberately left the guard alone.
Three semantically equivalent guards, two different ABC scores. The ==
and > are counted by csharp_count_token_condition's EQEQ | BANGEQ and GT | LT arms respectively; IsEven(x) contains no such token, so it
contributes nothing.
Cyclomatic is uniform at 3 across all three — it counts the two non-discard
arms and the base, and never the guard.
Two defects, one fixture
Cyclomatic under-counts. A guarded arm has two exits (pattern fails;
pattern matches and guard fails) but contributes one decision. Compare
Rust and Python, which do count a match guard — measured as part of fix(abc/csharp): relational pattern operator double-counts against its arm #1383's sibling sweep, match n { x if x > 5 => 1, _ => 0 } scores cyclomatic 3, and C#'s equivalent scores 2. So C# is the outlier among
the languages here, not the convention.
ABC is spelling-dependent. Whatever is decided for (1), ABC should
score a guard by what it is, not by whether its body happens to contain a
token an unrelated arm matches.
Where
src/metrics/cyclomatic/csharp.rs — counts SwitchExpressionArm (and the
statement switch's Case), with no when_clause arm.
src/metrics/abc/csharp.rs, csharp_count_token_condition — the token
arms that incidentally fire inside a guard.
csharp_switch_arm_guard_operator_still_counts in src/metrics/abc.rs
pins the current behaviour and cross-references this issue, so a fix here
must update that test deliberately rather than discovering it.
Note for whoever takes this
.claude/rules/grammar-dispatch.md §8 asks for abc.conditions == cyclomatic() - 1 per space, and the temptation will be
to reach it by suppressing ABC's guard token. That is the wrong direction:
it would make tok and cmp agree with a cyclomatic count that is itself
too low. Fix cyclomatic first, then re-derive what ABC should say.
Both changes move published metrics for C#, so each wants a corpus pass.
Be aware the DeepSpeech corpus contains no switch expressions at all
(established in #1383), so it cannot measure either.
Summary
A
whenguard on a C#switcharm is a real branch — the pattern can matchwhile the guard fails — and neither metric models it. C# cyclomatic has
no
when_clausearm, and C# ABC has no guard rule. ABC nonetheless appearsto count some guards, because its token arms fire on whatever operator
happens to sit inside one. The result is that the score depends on how the
guard is spelled rather than on what it does.
Found while fixing #1383,
which deliberately left the guard alone.
Measured
At
HEADoffix/batch-2026-09-10(after #1383):abc.conditionscyclomatic()cyclomatic() - 1when x % 2 == 0when x > 2when IsEven(x)Three semantically equivalent guards, two different ABC scores. The
==and
>are counted bycsharp_count_token_condition'sEQEQ | BANGEQandGT | LTarms respectively;IsEven(x)contains no such token, so itcontributes nothing.
Cyclomatic is uniform at 3 across all three — it counts the two non-discard
arms and the base, and never the guard.
Two defects, one fixture
pattern matches and guard fails) but contributes one decision. Compare
Rust and Python, which do count a match guard — measured as part of
fix(abc/csharp): relational pattern operator double-counts against its arm #1383's sibling sweep,
match n { x if x > 5 => 1, _ => 0 }scorescyclomatic 3, and C#'s equivalent scores 2. So C# is the outlier amongthe languages here, not the convention.
score a guard by what it is, not by whether its body happens to contain a
token an unrelated arm matches.
Where
src/metrics/cyclomatic/csharp.rs— countsSwitchExpressionArm(and thestatement
switch'sCase), with nowhen_clausearm.src/metrics/abc/csharp.rs,csharp_count_token_condition— the tokenarms that incidentally fire inside a guard.
csharp_switch_arm_guard_operator_still_countsinsrc/metrics/abc.rspins the current behaviour and cross-references this issue, so a fix here
must update that test deliberately rather than discovering it.
Note for whoever takes this
.claude/rules/grammar-dispatch.md§8 asks forabc.conditions == cyclomatic() - 1per space, and the temptation will beto reach it by suppressing ABC's guard token. That is the wrong direction:
it would make
tokandcmpagree with a cyclomatic count that is itselftoo low. Fix cyclomatic first, then re-derive what ABC should say.
Both changes move published metrics for C#, so each wants a corpus pass.
Be aware the DeepSpeech corpus contains no
switchexpressions at all(established in #1383), so it cannot measure either.
Resolution
Status: Fixed (pending merge)
Commit: eb4468c (+ d08eb8e) on branch
fix/batch-2026-09-13