Skip to content

fix(getter/groovy): gate the Super arm on its Wildcard parent, as Java does #1419

Description

@dekobon

Correction (2026-09-11): the premise below is wrong — Groovy::Super is emitted, in a wildcard type bound (? super T), where it correctly bills as an operator. Both proposed options would break something. See the correction comment for the measurement and the right fix (a Java-style parent gate).

Summary

big-code-analysis-ast/src/getter/groovy.rs:137 lists Super in the
operator arm of get_op_type. The Groovy grammar never emits
Groovy::Super (id 74) for a super reference — it emits a plain
identifier (id 1) — so the arm is dead, and super is classified as an
operand via the identifier path.

That happens to be the right answer since #1380, which made a
super-reference an operand in Java, C# and Kotlin to match the eleven
other languages. But it is right by accident: the only thing keeping
Groovy on the correct side is that its grammar does not produce the kind
the arm names. A grammar bump that starts emitting a dedicated super
node would silently flip Groovy to operator and reintroduce exactly the
inconsistency #1380 closed — with no test failing, because no fixture
exercises a kind that is currently unreachable.

Measured

At the pinned grammars, on the integration branch carrying the #1380 fix:

class B extends A {
  B() { super(1) }
  def h() { super.g() }
}
$ bca dump --no-config -p g.groovy | rg ': super'
      │     │     ├─ {identifier:1} from (2, 9) to (2, 14) : super
      │     │     │  ├─ {identifier:1} from (3, 13) to (3, 18) : super

$ bca ops --no-config -p g.groovy
   |- operators
   |  |- ()  |- .  |- def  |- extends  |- {}
   |- operands
   |  |- 1  |- A  |- B  |- g  |- h  `- super

Both the constructor-delegation spelling and the qualified-access
spelling reach identifier. A.super.h() and super::h were checked
too, with the same result.

The enum variant exists (language_groovy.rs:88 Super = 74, rendered
at :404), so the arm compiles and reads as live.

Why this is the §2 case

.claude/rules/grammar-dispatch.md §2 covers the shape: a variant that
exists in the enum but the parser never emits. Its prescription is to
keep the defensive arm and pin its unreachability with an assertion
naming the rule, "otherwise a future grammar that promotes the rule
changes behaviour invisibly". That is precisely the risk here, and there
is no such pin.

Options

  1. Pin it. Keep the arm and add
    !ast_has_kind_id(&parser, Groovy::Super as u16) asserting the kind is
    unreachable, so a grammar bump that promotes it fails loudly instead of
    silently reclassifying.
  2. Move it. If the arm is kept for the day the grammar does emit it,
    move Super to the operand arm so the defensive path agrees with the
    live path — the answer fix(getter): self- and super-references split three ways across languages #1380 settled for every other language.

Option 2 plus option 1's assertion is probably right: agreement between
the two paths, and a loud failure if the grammar changes.

Check This at the same time — Groovy's this is also a plain
identifier and the same question applies.

Found while fixing #1380, which moved This / Super / Base to the
operand arm in Java, C# and Kotlin. Not fixed there because Groovy needed
no behaviour change and a dead-arm cleanup is a different claim from a
reclassification.


Resolution

Fixed in 7f2a1feb (branch fix/issue-1419, integration branch
fix/batch-2026-09-14).

Super is pulled out of the ungated operator alternation in
big-code-analysis-ast/src/getter/groovy.rs and given Java's parent
gate:

Super if ancestors.parent_has_kind(node, Wildcard as u16) => TokenRole::Operator,
Super => TokenRole::Operand,

Groovy::Wildcard = 233 has no numeric-suffix alias. The gate uses
Ancestors::parent_has_kind — the same O(1) chain lookup Java's
ancestors.parent(node).map(...) performs, and the spelling the
SLASH slashy-string guard in the same match already uses.

No metric moves. The corrected premise in the comments above is
confirmed against the grammar source: dekobon-tree-sitter-groovy 0.2.2
spells 'super' only inside the wildcard production, so every
super-reference was already a plain identifier and already an operand.
The fix is forward compatibility — it removes the grammar accident that
a bump routing a reference to kind 74 would have turned into a
misclassification. The workspace suite is green with no
integration-snapshot drift.

Tests. groovy_wildcard_super_bound_stays_an_operator gains a
reference-direction assertion over all five super-reference spellings;
its bound half now exercises the gate's operator branch and was verified
by inverting the gate (perturbation, not a file-level revert). The
gate's operand branch is unreachable under the pin — only error recovery
on invalid Groovy (List<? super>) detaches the token from its
wildcard — so it is left untested per grammar-dispatch section 6,
with the existing node census as the drift marker.

Side effect. .rustfmt-bail-baseline.txt ratcheted for
getter/groovy.rs 5 → 7: two more arms in a match that already bails on
pre-existing in-pattern section comments, exactly as the same split did
for Java.

make pre-commitBCA_GATE: pass.

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