From c0f150e1b0cda57a96277f6f0998b17191cc8365 Mon Sep 17 00:00:00 2001 From: Diao Shengjia <104132148+GoDiao@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:12:08 +0800 Subject: [PATCH] fix(go): resolve field-chain calls on unexported method receivers (#2323) The Go typed-parameter receiver pattern is PascalCase-guarded, so `func (s *server)` yielded no receiver type and `s.service.AddItem()` was dropped. Add a receiver pattern anchored on `func (` that accepts unexported and generic receiver types; plain parameters keep the guard. --- CHANGELOG.md | 3 ++ __tests__/resolution.test.ts | 67 ++++++++++++++++++++++++++++++++++ src/resolution/name-matcher.ts | 4 ++ 3 files changed, 74 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ca319d98..623a2fc030 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixes + +- Go calls made through a struct field, like `s.service.AddItem()`, now resolve when the method's receiver type is unexported (`func (s *server) ...`), the usual shape for gRPC and HTTP handlers. These calls were missing from callers and impact before. Re-index Go projects after upgrading. (#2323) ## [1.6.2] - 2026-10-03 diff --git a/__tests__/resolution.test.ts b/__tests__/resolution.test.ts index 14b5198f47..71164e26c0 100644 --- a/__tests__/resolution.test.ts +++ b/__tests__/resolution.test.ts @@ -3428,6 +3428,73 @@ func (mx *Mux) dispatch() { fs.rmSync(tmpDir, { recursive: true, force: true }); } }, 30000); + + it('resolves field chains when the method receiver type is unexported (#2323)', async () => { + // `func (s *server)` is the idiomatic gRPC/HTTP handler shape. The + // typed-parameter pattern is PascalCase-guarded, so a lowercase receiver + // type used to yield no base type and the whole chain was dropped. + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-2323-')); + try { + fs.writeFileSync(path.join(tmpDir, 'go.mod'), 'module example.com/app\n\ngo 1.22\n'); + fs.mkdirSync(path.join(tmpDir, 'domain')); + fs.mkdirSync(path.join(tmpDir, 'handlers')); + fs.writeFileSync( + path.join(tmpDir, 'domain', 'service.go'), + `package domain + +type Service struct{} + +func (s *Service) AddItem(name string) error { return nil } + +func (s *Service) Count() int { return 0 } +` + ); + fs.writeFileSync( + path.join(tmpDir, 'handlers', 'server.go'), + `package handlers + +import "example.com/app/domain" + +type server struct { + service *domain.Service +} + +func (s *server) Create(name string) error { + return s.service.AddItem(name) +} + +type cache[T any] struct { + service *domain.Service +} + +func (c *cache[T]) Size() int { + return c.service.Count() +} +` + ); + + const cg = CodeGraph.initSync(tmpDir); + await cg.indexAll(); + + const addItem = (await cg.searchNodes('AddItem', { limit: 5 })).find( + (r) => r.node.kind === 'method' + ); + expect(addItem).toBeDefined(); + const addCallers = await cg.getCallers(addItem!.node.id); + expect(addCallers.map((c) => c.node.name)).toContain('Create'); + + // Generic receiver: the type parameter list must not hide the type. + const count = (await cg.searchNodes('Count', { limit: 5 })).find( + (r) => r.node.kind === 'method' + ); + expect(count).toBeDefined(); + const countCallers = await cg.getCallers(count!.node.id); + expect(countCallers.map((c) => c.node.name)).toContain('Size'); + cg.close(); + } finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + }, 30000); }); describe('Imported singleton instance-method calls (#1292)', () => { diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts index e0b4ad28b8..c0bd588fa7 100644 --- a/src/resolution/name-matcher.ts +++ b/src/resolution/name-matcher.ts @@ -6770,6 +6770,10 @@ function buildLocalReceiverTypePatterns(language: Language, r: string): RegExp[] return [ new RegExp(`\\b${r}\\b\\s*:=\\s*&?([A-Za-z_][\\w.]*)\\s*{`), // lg := Logger{} / &Logger{} new RegExp(`\\bvar\\s+${r}\\s+\\*?([A-Za-z_][\\w.]*)`), // var lg Logger / *Logger + // A method receiver: anchored on `func (`, so an unexported type + // (`func (s *server)`, `func (c *cache[T])`) is safe to accept here, + // unlike the keyword-free pattern below (#2323). + new RegExp(`\\bfunc\\s*\\(\\s*${r}\\s+\\*?([A-Za-z_]\\w*)\\s*(?:\\[[^\\]]*\\])?\\s*\\)`), // func (s *server) // A typed parameter / method receiver (`func use(lg Logger)`, // `func (l Logger) M()`) — name-before-type with no `var`/`:=` (#1125). // PascalCase-guarded (unlike the anchored patterns above) to keep the