Summary
#1297 fixed
public static bool operator <(V a, V b) scoring an ABC condition, and its
method was an enumeration of "the set of productions that can emit a bare <
or >". C# can overload six comparison operators, and the other four are
spelled with different tokens — GTEQ, LTEQ, EQEQ, BANGEQ — which
reach a separate, ungated arm of csharp_count_token_condition. They still
score a spurious condition each.
Found while measuring #1383,
whose fix gated GTEQ / LTEQ against a RelationalPattern parent but
deliberately did not widen into this.
Measured
At HEAD of fix/batch-2026-09-10 (after #1383):
class V {
public static bool operator <(V a, V b) { return true; }
public static bool operator >(V a, V b) { return true; }
public static bool operator <=(V a, V b) { return true; }
public static bool operator >=(V a, V b) { return true; }
public static bool operator ==(V a, V b) { return true; }
public static bool operator !=(V a, V b) { return true; }
}
| declaration |
abc.conditions |
cyclomatic() |
should be |
operator < |
0 |
1 |
0 ✓ |
operator > |
0 |
1 |
0 ✓ |
operator <= |
1 |
1 |
0 ✗ |
operator >= |
1 |
1 |
0 ✗ |
operator == |
1 |
1 |
0 ✗ |
operator != |
1 |
1 |
0 ✗ |
bca dump confirms the shape — the token is a direct child of
operator_declaration, with no binary_expression anywhere:
{operator_declaration:256} : public static bool operator >=(V a, V b) { … }
├─ {operator:61} : operator
├─ {>=:82} : >=
Where
csharp_count_token_condition, src/metrics/abc/csharp.rs. After #1383 the
arms read:
EQEQ | BANGEQ | Else | Case | Try | Catch => { stats.conditions += 1.; }
GTEQ | LTEQ if !ancestors.parent_has_kind(node, RelationalPattern as u16) => { … }
GT | LT if <parent is BinaryExpression | BinaryExpression2> => { … }
Only the third has an allowlist, and only the third is #1297-clean. The
declaration's operator token is the operator being defined, not applied,
exactly as #1297 argued for < / >.
Suggested fix
Give GTEQ / LTEQ / EQEQ / BANGEQ the same BinaryExpression | BinaryExpression2 allowlist polarity GT / LT already has, which
subsumes #1383's RelationalPattern denial and fails closed on a grammar
bump (.claude/rules/grammar-dispatch.md §1). Enumerate the productions
that can emit each of the four tokens at the pinned =0.23.5 first — at
minimum binary_expression, relational_pattern (the first two only) and
operator_declaration — and confirm nothing else legitimately needs to
count.
This changes a published metric, so it wants the same corpus pass #1383 got.
Note the DeepSpeech corpus is useless for it: it contains no comparison
operator overloads either.
Sibling sweep
#1297's C# row was the only one whose construct is spelled with more than
one token family, but check whether Java's #1274 allowlist has the same gap
before closing — operator overloading does not exist in Java, but Groovy's
compareTo / equals spellings and Kotlin's operator fun compareTo should
be measured.
Resolution
Status: Fixed (pending merge)
Commit: f04a007 on branch fix/batch-2026-09-13
Root cause: the six comparison tokens were split across two arms of
csharp_count_token_condition with different gate polarity; the ungated and
denylisted arms let operator_declaration through for <= >= == !=.
All six now share one binary_expression parent allowlist.
Summary
#1297 fixed
public static bool operator <(V a, V b)scoring an ABC condition, and itsmethod was an enumeration of "the set of productions that can emit a bare
<or
>". C# can overload six comparison operators, and the other four arespelled with different tokens —
GTEQ,LTEQ,EQEQ,BANGEQ— whichreach a separate, ungated arm of
csharp_count_token_condition. They stillscore a spurious condition each.
Found while measuring #1383,
whose fix gated
GTEQ/LTEQagainst aRelationalPatternparent butdeliberately did not widen into this.
Measured
At
HEADoffix/batch-2026-09-10(after #1383):abc.conditionscyclomatic()operator <operator >operator <=operator >=operator ==operator !=bca dumpconfirms the shape — the token is a direct child ofoperator_declaration, with nobinary_expressionanywhere:Where
csharp_count_token_condition,src/metrics/abc/csharp.rs. After #1383 thearms read:
Only the third has an allowlist, and only the third is #1297-clean. The
declaration's operator token is the operator being defined, not applied,
exactly as #1297 argued for
</>.Suggested fix
Give
GTEQ/LTEQ/EQEQ/BANGEQthe sameBinaryExpression | BinaryExpression2allowlist polarityGT/LTalready has, whichsubsumes #1383's
RelationalPatterndenial and fails closed on a grammarbump (
.claude/rules/grammar-dispatch.md§1). Enumerate the productionsthat can emit each of the four tokens at the pinned
=0.23.5first — atminimum
binary_expression,relational_pattern(the first two only) andoperator_declaration— and confirm nothing else legitimately needs tocount.
This changes a published metric, so it wants the same corpus pass #1383 got.
Note the DeepSpeech corpus is useless for it: it contains no comparison
operator overloads either.
Sibling sweep
#1297's C# row was the only one whose construct is spelled with more than
one token family, but check whether Java's #1274 allowlist has the same gap
before closing —
operatoroverloading does not exist in Java, but Groovy'scompareTo/equalsspellings and Kotlin'soperator fun compareToshouldbe measured.
Resolution
Status: Fixed (pending merge)
Commit: f04a007 on branch
fix/batch-2026-09-13Root cause: the six comparison tokens were split across two arms of
csharp_count_token_conditionwith different gate polarity; the ungated anddenylisted arms let
operator_declarationthrough for<=>===!=.All six now share one
binary_expressionparent allowlist.