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.
…er cases Add tests pinning the interface branch inside grouped type blocks (mixed struct+interface and two-interface groups) and multi-parameter / constrained generic method receivers (Box[K, V], Result[T comparable]). Document in extractTypeDeclaration that non-struct/non-interface specs (named-primitive type_spec and type_alias nodes) are intentionally not modeled as classes, and that the per-spec declNode lets grouped types report their own line ranges. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
1. Grouped non-struct/non-interface specs. You're right that 2. Shallow grouped-declaration coverage. Added two tests: a grouped block mixing 3. Multi-param / constrained generic receivers. Added tests for Nit (declNode → type_spec). The shift to the per-spec node is deliberate and, for grouped declarations, more correct: each grouped type now reports its own line range instead of all sharing the opening Full core suite: 698 passed. tsc clean. |
… exports Previously only struct_type / interface_type specs were captured; exported defined types (`type Count int`) and type aliases (`type MyID = string`, which parse as a separate `type_alias` node) were silently dropped from `exports` — both in grouped `type ( ... )` blocks and as standalone declarations. These are public package symbols and now appear in `exports` (still not in `classes`, since they have no fields/methods). Unexported (lowercase) names remain excluded. Addresses review concern #1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Follow-up on concern 1 — promoted from documented-gap to an actual fix in On a closer look the silent-drop was broader than the grouped block: exported defined types ( |
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