Skip to content

fix(abc/csharp): comparison-operator overloads other than < / > still score a condition #1420

Description

@dekobon

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions