fix(go-extractor): attach methods on generic receivers and handle grouped type declarations#445
Conversation
…uped type declarations
extractReceiverType only unwrapped a bare type_identifier or a
pointer_type directly wrapping one. Generic receivers like
`(s *Stack[T])` or `(s Stack[T])` are parsed as generic_type nodes
(optionally pointer-wrapped), so the base type name was never found
and the method was never attached to its struct (methods stayed []).
The fix iteratively unwraps pointer_type and generic_type layers
before grabbing the base type_identifier.
extractTypeDeclaration used findChild(node, "type_spec"), which only
returns the first type_spec. Grouped declarations
(`type ( Foo struct{...}; Bar struct{...} )`) contain multiple
type_spec children, so every type after the first was silently
dropped from classes and exports. The fix iterates over all type_spec
children via findChildren and passes each typeSpec as the decl node
for accurate per-type line ranges.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thejesh23
left a comment
There was a problem hiding this comment.
1. Grouped type aliases / non-struct-non-interface specs are silently dropped.
extractTypeDeclaration now iterates all type_spec children but still only branches on struct_type / interface_type. A type ( Foo struct{...}; MyID = string; Count int ) block records Foo and drops MyID and Count without any signal — the same silent-drop pattern the PR is fixing for grouped structs. Worth either handling type aliases/named primitives or at least a comment noting the gap.
2. Test coverage for grouped declarations is shallow.
Only an all-struct group is exercised. A grouped block mixing struct and interface (e.g. type ( Foo struct{...}; Bar interface{...} )), or two grouped interfaces, would catch regressions in the interface branch of the new loop — currently nothing pins extractInterface(typeSpec, ...) against the per-spec node change.
3. Multi-type-parameter and constrained generic receivers are untested.
The new test covers Stack[T] only. func (s *Box[K, V]) ... and func (s Result[T comparable]) ... are common Go 1.18+ patterns; both should fall out of the same generic_type -> type field traversal, but without a test there's nothing guarding it. (Same class of issue you'll hit in #435 for Dart generic type parameters on methods — worth keeping the receiver-unwrap pattern consistent across extractors.)
Nit: extractTypeDeclaration now passes typeSpec (not the outer type_declaration) as declNode, so for non-grouped type Foo struct{...} the recorded lineRange[0] is the name line rather than the type keyword line. Existing tests happen to pass because type and the name are on the same line, but the semantics shifted silently.
Problem
Two distinct gaps in the Go extractor caused real-world Go constructs to be silently dropped from the structural graph:
Methods on generic receivers were never attached to their struct.
extractReceiverTypeonly handled a baretype_identifierreceiver or apointer_typedirectly wrapping atype_identifier. For a generic type the receiver is ageneric_typenode —func (s Stack[T]) ...parses asparameter_declaration -> generic_type -> {type_identifier 'Stack', type_arguments}andfunc (s *Stack[T]) ...aspointer_type -> generic_type -> type_identifier. Neither matched, so the function returnedundefined, nothing was recorded inmethodsByReceiver, and the struct'smethodsarray stayed empty. AStack[T]struct withPush/Lenmethods producedclasses[0].methods === [].Grouped type declarations dropped every type after the first.
extractTypeDeclarationusedfindChild(node, "type_spec"), which returns only the firsttype_spec. Go allows grouping types under onetype ( ... )block, which parses as onetype_declarationwith severaltype_specchildren. Sotype ( Foo struct{...}; Bar struct{...} )producedclasses: ['Foo']andexports: ['Foo'], withBarmissing entirely.Fix
extractReceiverType: iteratively unwrappointer_typeandgeneric_typelayers (descendinggeneric_typevia itstypefield) before grabbing the basetype_identifier. Non-generic receivers are unaffected.extractTypeDeclaration: iterate over alltype_specchildren viafindChildren(already imported), and pass eachtypeSpecas the decl node so line ranges are per-type rather than the whole group.Both changes are minimal and localized to
go-extractor.ts.Testing
Added two tests to
go-extractor.test.ts:attaches methods on generic receivers to their struct— aStack[T]struct with*Stack[T]andStack[T]receivers; expectsclasses[0].methodsto containPushandLen.handles grouped type declarations— atype ( Foo struct{...}; Bar struct{...} )block; expectsclassesto be['Foo','Bar']and exports to contain both.Both tests fail before the fix (
methodsis[]; onlyFoois captured) and pass after. The full core Vitest suite is green (694 tests),tsc --noEmitexits 0, and ESLint passes on both changed files.🤖 Generated with Claude Code