From 3a3b03f654229d858e4d7430d650ab7216636f1d Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Mon, 5 Oct 2026 15:56:53 -0500 Subject: [PATCH] fix(go): resolve unexported receivers and nested modules within the right package (#2323, #2322) Calls through a method receiver or parameter of an unexported type (`func (s *server) Create() { s.service.AddItem() }`) had no edge: the Go receiver inference only accepted PascalCase types (#2323). And only the project-root go.mod was read, so a module in a subdirectory, or one beside other modules as in etcd, treated imports of itself as third-party (#2322). Fixing either alone sends calls to the wrong package, because Go type names are unique only per directory while types and methods were looked up by name across the project. The receiver pattern alone sent 1,042 of harbor's 1,998 new calls to another package's same-named type; reading nested modules alone made harbor's caching wrappers call themselves and put about 180 of etcd's type references on same-named methods. Both share one fix: - A lowercase receiver or parameter type is read only where a parameter list puts it (receivers, parameters, multi-line lists, var blocks). - Receiver inference also reports the type as written. For Go, resolveMethodOnType takes the declaring package's directory (the caller's for a bare name, the import's for a qualified one) and counts only that package's methods, then the types its declaration embeds (struct and interface embedding, read from the source) instead of the name-based supertype union. The field-chain matcher scopes its struct and field types the same way. - The resolver reads the nearest go.mod of every directory holding indexed Go files (no disk walk; a module under testdata/ or a _/. directory serves only its own files) and maps an import path to the module with the longest module path, the importing file's own module breaking a tie. getGoPackageDir replaces getGoModule. - A bare name written through a package (`job.OPCommand`) resolves through the import into that package's directory, and a name-matched candidate outside the qualifier's package is rejected; a method reached through a variable that shadows an import is exempt. Verification: new go-unexported-receiver (3) and go-nested-module (11) tests: 12 fail on main (the 2 controls pass), all 14 pass. The issues' repros go from 0 to 1 caller; the root-go.mod and `Server` variants are unchanged. Validation, main 26e8488a -> this branch, whole repos, Go->Go edges keyed by source/target qualified name, file, line and column: - etcd 6bb7e5e: 52,530 -> 66,284 (+13,827 / -73), calls 18,074 -> 25,265 - harbor 721c6d4: 62,423 -> 77,615 (+15,819 / -627), calls 16,308 -> 25,236 - prometheus 770ca8f: 90,970 -> 92,399 (+2,249 / -820), calls 40,050 -> 41,493 Of the 1,520 removed edges, 1,458 are the same call site or reference now resolved to the right package's symbol; the other 62 were field reads taken for method values, names whose real target is not indexed (a `type T = string` alias), and a call to a method the receiver's type does not have. Indexing time and CPU are unchanged. Co-authored-by: Diao Shengjia <104132148+GoDiao@users.noreply.github.com> Co-authored-by: danusha2345 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 + __tests__/go-nested-module.test.ts | 331 +++++++++++++++++++++++ __tests__/go-unexported-receiver.test.ts | 140 ++++++++++ src/resolution/go-module.ts | 52 +++- src/resolution/import-resolver.ts | 86 +++--- src/resolution/index.ts | 80 +++++- src/resolution/name-matcher.ts | 300 ++++++++++++++++---- src/resolution/types.ts | 15 +- 8 files changed, 891 insertions(+), 115 deletions(-) create mode 100644 __tests__/go-nested-module.test.ts create mode 100644 __tests__/go-unexported-receiver.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 17cb9a1ff7..0913f0483c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - In VB.NET, reading or setting a `Shared` field or property through its class or module name, like `AppSession.SessionId`, `AppSession.CurrentUser = "demo"` or `AppSession.Items(0)`, now counts as a use of that member and of the class, and so do reading an `Enum` value like `Mode.Fast` and calling a `Shared` function without parentheses. Before, only calls written with parentheses were linked, so `codegraph callers` on such a field, property or class came back empty and impact missed most of the code that depends on it. A local, parameter or field that only shares a class's name is not mistaken for the class. Thanks @serkanince for the report and @ChrisPrapas. (#2305) - In Vue and Nuxt components, calls written in the template (`{{ formatDate(post) }}`, `:to="getAccountRoute(account)"`, `@click="save(item)"`) and calls in a destructuring declaration at the top of `