From 81aef613c582a305cec5c6cc9190932b5e607482 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Tue, 6 Oct 2026 23:24:39 -0500 Subject: [PATCH 1/2] fix(go): a type in a signature or composite literal links a type, never a method of its name A Go type position -- a parameter or result type, a composite literal's type -- names a type of the package Go reads it from. Name matching (and the instance-method guess, and the Express resolver reading Go refs) took whatever declaration shared the name: etcd's `func (ti *treeIndex) KeyIndex(keyi *keyIndex) *keyIndex` linked both types to the method `treeIndex.keyIndex`, prometheus's `samples{...}` literals to `sampleRing.samples`, and `semver.Version` or `config.URL{...}` from outside packages to project methods and functions of that name. gateTargetKind now hands every Go type position to goTypePositionTarget: a method or function target moves to the type of that name in the reference's own package (bare) or the imported project package (`pkg.T`), or is dropped when there is none; a bare name that found another package's type moves to its own package's type when it declares one (prometheus's `prompb` builds its own `Histogram_CountInt`). A generic method's receiver (`func (p *Pool[T]) Get() T`) now declares type parameters for gateTypeParameter. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + __tests__/go-type-position-kinds.test.ts | 245 +++++++++++++++++++++++ src/resolution/index.ts | 14 +- src/resolution/name-matcher.ts | 42 ++++ src/resolution/type-parameters.ts | 11 +- 5 files changed, 310 insertions(+), 3 deletions(-) create mode 100644 __tests__/go-type-position-kinds.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fe1144fe4a..3d4dbda8b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - In Go, calls into a module whose `go.mod` is not at the project root now resolve, whether it is a `server/` backend next to a `web/` frontend or one of several modules side by side as in etcd, so calls like `store.New()` and `s.db.CreateItem()` find their targets instead of being treated as calls into a third-party package. A name written through a package, like a `job.OPCommand` result type or a field of type `artifact.Manager`, now links to that package's symbol rather than a same-named one elsewhere, which also corrects links in projects with a single `go.mod`. Re-index Go projects after upgrading. Thanks @GoDiao for the report and @danusha2345 for the fix. (#2322) - In Go, an exported type written without a package, like `Node` in `func Walk(v Visitor, node Node)`, now links to the type of that name in its own package, whether it is a struct, an interface or another kind of type. Before, it could link to a struct of the same name in an unrelated package, so prometheus's PromQL parser functions pointed at the Kubernetes discovery `Node` struct instead of the parser's own `Node` interface, or, for a result type like `Appender` in `func (f *fanout) Appender(…) Appender`, to the method itself. A type from a package outside your project, like `apiv1.Node` or `http.Handler`, no longer links to a struct of the same name in the file that uses it; a variadic parameter like `...storage.Filter` now links to that package's type; a route handler written as a method value, like `h.Follow` in an Echo or Gin app, links to that method rather than a model struct named `Follow`; and a method called on another call's result or on an indexed value, like `err[i].Error()` or a chained `.String()`, is no longer recorded as creating a struct of the same name. Re-index Go projects after upgrading. - In Go, a function or method passed as a value, like `sync.Pool{New: wm.new}`, now shows the function that passes it among its callers and impact even when it is named `new`, `nil`, `None`, `self` or a handful of similar words, or when a line break or comment sits beside the dot. Before, such a value was skipped, so a method used only that way looked unused. Re-index Go projects after upgrading. +- In Go, a type in a parameter, a result or a composite literal now always links to a type, never to a method or function that shares its name. Before, an unexported type like `keyIndex` in etcd's `func (ti *treeIndex) KeyIndex(keyi *keyIndex) *keyIndex` linked to the `keyIndex` method declared beside it, prometheus's `samples{…}` literals to a `samples` method, and a type from a package outside your project, like `semver.Version` or `config.URL`, to whatever project method or function had that name. A type written without a package, including an unexported one or one named like `AppenderV2` or `Histogram_CountInt`, now links to its own package's type when there is one, and a generic method's type parameter, like `T` in `func (p *Pool[T]) Get() T`, no longer links to an unrelated `T`. Re-index Go projects after upgrading. - Indexing a project that includes large bundled JavaScript files, such as a copy of pdf.js or d3, is fast again: since 1.6.2, resolving the calls in a JavaScript or TypeScript file re-read the file's text above each call, so a single bundled library could add many seconds to an index. The graph it builds is unchanged. Thanks @bompus for the report. (#2334) - The time `codegraph init` and `codegraph index` print beside the node and edge counts now covers the whole run, resolving references and linking included, and `codegraph sync` reports its whole run the same way. Before, it counted only reading and parsing the files, which can be a small part of an index, so a slow index looked fast. Thanks @bompus for the report. (#2334) - In Rust, code that uses an enum only through its variants, like `mode::Mode::A` in an expression, a `Mode::B =>` match arm or `Self::A` inside the enum's own `impl`, now shows up in that enum's callers and impact, linked to the enum that actually declares the variant rather than to a same-named type elsewhere. Standard-library variants like `Ordering::Less` or `Option::Some` and associated items like `Foo::new()` or `Foo::MAX` don't count as using a project enum. Re-index Rust projects after upgrading. Thanks @mg-mg-mg for the report and @danusha2345. (#2328) diff --git a/__tests__/go-type-position-kinds.test.ts b/__tests__/go-type-position-kinds.test.ts new file mode 100644 index 0000000000..e7a0386ae1 --- /dev/null +++ b/__tests__/go-type-position-kinds.test.ts @@ -0,0 +1,245 @@ +/** + * A Go type position — a parameter or result type, a composite literal's + * type — names a type: a struct, an interface or a defined type, of the + * package Go reads the name from. Never a method or a function, which Go + * reaches only through a value or a package. Name matching took whatever + * declaration shared the name: etcd's + * `func (ti *treeIndex) KeyIndex(keyi *keyIndex) *keyIndex` linked both + * `keyIndex` types to the method `treeIndex.keyIndex` beside it, prometheus's + * `(ec2Client, error)` result to the method `EC2Discovery.ec2Client` it + * declares, every `samples{…}` literal to `sampleRing.samples`, and + * `&config_util.URL{…}` (an outside package) to `Target.URL`. A generic + * receiver's `[T]` declares `T` too: `func (p *Pool[T]) Get() T` returns the + * pool's own element type, not the method `Sample.T` of another package. + */ +import { describe, it, expect, afterAll, beforeAll } from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { CodeGraph } from '../src'; + +let root = ''; +let cg: CodeGraph; + +beforeAll(async () => { + root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-go-type-pos-')); + const files: Record = { + 'go.mod': 'module example.com/app\n\ngo 1.22\n', + 'mvcc/key_index.go': `package mvcc + +type keyIndex struct { + key []byte +} + +type revisions []int64 +`, + 'mvcc/index.go': `package mvcc + +type treeIndex struct { + tree map[string]*keyIndex +} + +func (ti *treeIndex) KeyIndex(keyi *keyIndex) *keyIndex { + return ti.keyIndex(keyi) +} + +func (ti *treeIndex) keyIndex(keyi *keyIndex) *keyIndex { + return &keyIndex{key: keyi.key} +} + +func (ti *treeIndex) revisions() revisions { + return revisions{1, 2} +} +`, + 'mvcc/watchable_store.go': `package mvcc + +type contains interface { + contains(rev int64) bool +} + +func kvsToEvents(c contains, revs [][]byte) {} +`, + 'discovery/aws/ec2.go': `package aws + +import "context" + +type ec2Client interface { + DescribeInstances(ctx context.Context) error +} + +type EC2Discovery struct { + client ec2Client +} + +func (d *EC2Discovery) ec2Client(ctx context.Context) (ec2Client, error) { + return d.client, nil +} +`, + 'util/zeropool/pool.go': `package zeropool + +type Pool[T any] struct { + items []T +} + +func (p *Pool[T]) Get() T { + var zero T + return zero +} + +func (p *Pool[T]) Put(item T) {} + +type LazyLoader[K comparable, V any] struct{} + +func (l *LazyLoader[K, V]) Load(key K) (V, error) { + var v V + return v, nil +} +`, + 'prompb/types.pb.go': `package prompb + +type Histogram struct { + Count isHistogram_Count +} + +type isHistogram_Count interface { + isHistogram_Count() +} + +type Histogram_CountInt struct { + CountInt uint64 +} + +func (*Histogram_CountInt) isHistogram_Count() {} + +func (m *Histogram) GetCount() isHistogram_Count { + return m.Count +} +`, + 'prompb/codec.go': `package prompb + +func FromIntHistogram(count uint64) Histogram { + return Histogram{ + Count: &Histogram_CountInt{CountInt: count}, + } +} +`, + 'prompb/io/prometheus/write/v2/types.pb.go': `package writev2 + +type Histogram_CountInt struct { + CountInt uint64 +} + +type Sample struct { + ts int64 +} + +func (s Sample) T() int64 { return s.ts } +`, + 'model/value/value.go': `package value + +type T struct{} + +type K struct{} +`, + 'storage/remote/client_test.go': `package remote + +import ( + "testing" + + config_util "github.com/prometheus/common/config" +) + +type ClientConfig struct { + URL *config_util.URL +} + +func TestStoreHTTPErrorHandling(t *testing.T) { + conf := &ClientConfig{ + URL: &config_util.URL{URL: nil}, + } + _ = conf +} +`, + 'scrape/target.go': `package scrape + +type Target struct{} + +func (t *Target) URL() string { return "" } +`, + }; + for (const [rel, content] of Object.entries(files)) { + fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true }); + fs.writeFileSync(path.join(root, rel), content); + } + cg = await CodeGraph.init(root, { index: true }); +}); + +afterAll(() => { + cg?.close(); + if (root) fs.rmSync(root, { recursive: true, force: true }); +}); + +/** The nodes of `file`, or the one named `from`. */ +const sourcesIn = (file: string, from?: string) => cg.getNodesInFile(file).filter((n) => !from || n.name === from); + +/** + * `kind file:qualifiedName` of each type-position edge (a `references` or an + * `instantiates`) out of `file`'s nodes whose target is named `name`, + * deduplicated and sorted. + */ +const typeLinksFrom = (file: string, name: string, from?: string) => { + const links = cg.getOutgoingEdgesFrom(sourcesIn(file, from).map((n) => n.id)) + .filter((e) => e.kind === 'references' || e.kind === 'instantiates') + .map((e) => ({ kind: e.kind, target: cg.getNode(e.target)! })) + .filter(({ target }) => target.name === name) + .map(({ kind, target }) => `${kind} ${target.filePath}:${target.qualifiedName}`); + return [...new Set(links)].sort(); +}; + +describe('A Go type position resolves to a type', () => { + it('an unexported parameter and result type is its package’s struct, not a method of that name', () => { + expect(typeLinksFrom('mvcc/index.go', 'keyIndex', 'KeyIndex')).toEqual(['references mvcc/key_index.go:keyIndex']); + expect(typeLinksFrom('mvcc/index.go', 'keyIndex', 'keyIndex')).toEqual([ + 'instantiates mvcc/key_index.go:keyIndex', + 'references mvcc/key_index.go:keyIndex', + ]); + }); + + it('the method a call names stays a method', () => { + const calls = cg.getOutgoingEdgesFrom(sourcesIn('mvcc/index.go', 'KeyIndex').map((n) => n.id)) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.target)!.qualifiedName); + expect(calls).toEqual(['treeIndex::keyIndex']); + }); + + it('a defined type in a result and a composite literal is that type', () => { + expect(typeLinksFrom('mvcc/index.go', 'revisions', 'revisions')).toEqual([ + 'instantiates mvcc/key_index.go:revisions', + 'references mvcc/key_index.go:revisions', + ]); + }); + + it('an interface whose method shares its name is the interface', () => { + expect(typeLinksFrom('mvcc/watchable_store.go', 'contains', 'kvsToEvents')).toEqual(['references mvcc/watchable_store.go:contains']); + expect(typeLinksFrom('prompb/types.pb.go', 'isHistogram_Count', 'GetCount')).toEqual(['references prompb/types.pb.go:isHistogram_Count']); + }); + + it('a result type is the interface, not the method the line declares', () => { + expect(typeLinksFrom('discovery/aws/ec2.go', 'ec2Client', 'ec2Client')).toEqual(['references discovery/aws/ec2.go:ec2Client']); + }); + + it('a bare composite literal is its own package’s struct, not a namesake in another package', () => { + expect(typeLinksFrom('prompb/codec.go', 'Histogram_CountInt')).toEqual(['instantiates prompb/types.pb.go:Histogram_CountInt']); + }); + + it('a generic receiver’s type parameters are type parameters', () => { + expect(typeLinksFrom('util/zeropool/pool.go', 'T', 'Get')).toEqual([]); + expect(typeLinksFrom('util/zeropool/pool.go', 'T', 'Put')).toEqual([]); + expect(typeLinksFrom('util/zeropool/pool.go', 'K', 'Load')).toEqual([]); + expect(typeLinksFrom('util/zeropool/pool.go', 'V', 'Load')).toEqual([]); + }); + + it('a composite literal of an outside package’s type is no project method of that name', () => { + expect(typeLinksFrom('storage/remote/client_test.go', 'URL')).toEqual([]); + }); +}); diff --git a/src/resolution/index.ts b/src/resolution/index.ts index d8eca46e4a..0584149a8d 100644 --- a/src/resolution/index.ts +++ b/src/resolution/index.ts @@ -21,7 +21,7 @@ import { isImportableKind, CPP_DEFINE_SIGNATURE, } from './types'; -import { isPythonSelfCall, matchJsStoreBindingCall, isUnresolvedJsMemberCall, matchObjectPathCall, thisScopeCaller, isVisibleAcrossFiles, matchReference, matchFunctionRef, matchDottedCallChain, matchScopedCallChain, matchMethodCall, sameLanguageFamily, crossesCodeBoundary, gateLanguageMatch, dumpNameMatcherProfile, clearNameMatcherMemos, isRustNameInScope, CASE_INSENSITIVE_LANGUAGES, isDartMemberRead, matchDartMemberRead, isDartChainLink, matchDartChainLink, isDartAnnotation, matchDartAnnotation, isStdMethodName, isGoUnknownQualified, isGoBareName, GO_TYPE_KINDS } from './name-matcher'; +import { isPythonSelfCall, matchJsStoreBindingCall, isUnresolvedJsMemberCall, matchObjectPathCall, thisScopeCaller, isVisibleAcrossFiles, matchReference, matchFunctionRef, matchDottedCallChain, matchScopedCallChain, matchMethodCall, sameLanguageFamily, crossesCodeBoundary, gateLanguageMatch, dumpNameMatcherProfile, clearNameMatcherMemos, isRustNameInScope, CASE_INSENSITIVE_LANGUAGES, isDartMemberRead, matchDartMemberRead, isDartChainLink, matchDartChainLink, isDartAnnotation, matchDartAnnotation, isStdMethodName, isGoUnknownQualified, isGoBareName, goTypePositionTarget, GO_TYPE_KINDS } from './name-matcher'; import { isVisibleCppMacro, clearCppMacroVisibility } from './cpp-macro-visibility'; import { isCppConstructorRef, matchCppConstructor } from './cpp-constructor'; import { isCppSupertypeRef, matchCppSupertype, clearCppSupertypeMemos } from './cpp-supertypes'; @@ -3066,6 +3066,10 @@ export class ReferenceResolver { * inside a type never is, and neither is the import statement the reference * was written in. * + * For a Go type position: the target is a type of the package Go reads the + * name from, moved there when a strategy found a method, a function or + * another package's type (see goTypePositionTarget). + * * For `extends`/`implements`, it cannot be describing a real supertype when: * * 1. The target's kind can never be a supertype (an enum member, a method, @@ -3117,6 +3121,14 @@ export class ReferenceResolver { return target && !isImportableKind(target.kind) ? null : result; } + // A Go type position — a parameter or result type, a composite literal's + // type — names a type of the package Go reads it from (route handlers are + // `references` too, but values). + if (ref.language === 'go' && (ref.referenceKind === 'instantiates' || + (ref.referenceKind === 'references' && this.nodeById(ref.fromNodeId)?.kind !== 'route'))) { + return goTypePositionTarget(result, ref, this.context); + } + if (!isInheritanceRef(ref)) return result; const target = this.nodeById(result.targetNodeId); if (target && !isSupertypeTarget(target)) { diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts index 08dc5575fd..8360e695ab 100644 --- a/src/resolution/name-matcher.ts +++ b/src/resolution/name-matcher.ts @@ -1936,6 +1936,48 @@ export function isGoUnknownQualified(ref: UnresolvedRef, context: ResolutionCont return written !== undefined && imported === undefined; } +/** + * What a Go type position — a parameter or result type, a composite + * literal's type — names: a type, which Go reads from one package. A method + * or a function is never it; Go reaches those only through a value or a + * package. Whichever strategy found a declaration of the name, the type is + * the one of that name in the reference's own package for a bare name, or in + * the imported project package for `pkg.T`. Without one there, a method or + * function of the name is nothing the reference means. etcd's + * `func (ti *treeIndex) KeyIndex(keyi *keyIndex) *keyIndex` linked both + * `keyIndex` types to the method `treeIndex.keyIndex` beside it, + * prometheus's `(ec2Client, error)` result to the method the line declares, + * and its `&config_util.URL{…}` (an outside package) to `Target.URL`. A bare + * name that found another package's type means its own package's type of + * that name when there is one: prometheus's `prompb` builds its own + * `Histogram_CountInt`, not the `write/v2` one. + */ +export function goTypePositionTarget(result: ResolvedRef, ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null { + const target = context.getNodeById?.(result.targetNodeId); + if (!target || target.language !== 'go') return result; + const isType = GO_TYPE_KINDS.has(target.kind); + // A composite literal keeps its package in the name (`config_util.URL`); + // a parameter type leaves it on the line. + const dot = ref.referenceName.lastIndexOf('.'); + const name = ref.referenceName.slice(dot + 1); + // The package's directory; null for a package outside the project, + // undefined for a qualifier that is none of the file's imports as indexed. + let pkgDir: string | null | undefined; + let bare = false; + if (dot >= 0) { + pkgDir = goImportPackageDir(ref.referenceName.slice(0, dot), ref.filePath, context); + } else { + const { written, imported } = goRefQualification(ref, context); + bare = written === undefined; + if (bare) pkgDir = goPackageDir(ref.filePath); + else if (imported) pkgDir = context.getGoPackageDir?.(imported.source, ref.filePath) ?? null; + } + if (isType && (!bare || goPackageDir(target.filePath) === pkgDir)) return result; + const types = pkgDir ? goPackageTypes(name, pkgDir, context) : []; + if (types.length > 0) return { ...result, targetNodeId: preferCallSiteFile(types, ref.filePath)[0]!.id }; + return isType ? result : null; +} + /** * Whether a Go reference is written through an imported package from outside * the project's modules — `context.Context`, `fmt.Errorf`, a third-party diff --git a/src/resolution/type-parameters.ts b/src/resolution/type-parameters.ts index 8ad1aaa041..d5c4a9ba10 100644 --- a/src/resolution/type-parameters.ts +++ b/src/resolution/type-parameters.ts @@ -9,8 +9,9 @@ * from the declaration heads around the reference: after the declared name * (`class Foo`, `fn f`, `func F[T any]`, `def f[F[_]: Monad, A]`, * `struct Stack`), before it where the language puts them - * (Java's ` T max(…)`, Kotlin's `fun f()`), and a C++ - * `template ` line above it. Applied at resolveOne's seam, so every + * (Java's ` T max(…)`, Kotlin's `fun f()`), a C++ + * `template ` line above it, and a Go method's receiver + * (`func (p *Pool[T]) Get() T`). Applied at resolveOne's seam, so every * strategy's result obeys it. */ import type { Node } from '../types'; @@ -152,6 +153,12 @@ function declaredTypeParameters(node: Node, context: ResolutionContext): Readonl if (before[i] === '<' && (i === 0 || /[\s(,]/.test(before[i - 1]!))) collect(before, i, names); } } + // Go: a method of a generic type declares the type's parameters again in + // its receiver — `func (p *Pool[T]) Get() T`, `func (LazyLoader[K, V]) …`. + if (node.language === 'go' && node.kind === 'method') { + const receiver = /^\s*func\s*\(\s*(?:[A-Za-z_]\w*\s+)?\*?\s*[A-Za-z_]\w*\s*\[/.exec(head); + if (receiver) collect(head, receiver[0].length - 1, names); + } // C++: `template ` on the lines just above. if (node.language === 'cpp') { const above = lines.slice(Math.max(0, node.startLine - 3), node.startLine).join('\n'); From 65b053d7c8408f8c65ec03bf34274da74360a5f7 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Wed, 7 Oct 2026 03:42:17 -0500 Subject: [PATCH 2/2] docs(changelog): cite an outside-package type this change fixes `semver.Version` reads as an outside package since #2410, so its links to project methods are gone before this change; `config.URL{...}` still went to `scrape.Target.URL`. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17538d4037..882c6a7464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - In Go, calls into a module whose `go.mod` is not at the project root now resolve, whether it is a `server/` backend next to a `web/` frontend or one of several modules side by side as in etcd, so calls like `store.New()` and `s.db.CreateItem()` find their targets instead of being treated as calls into a third-party package. A name written through a package, like a `job.OPCommand` result type or a field of type `artifact.Manager`, now links to that package's symbol rather than a same-named one elsewhere, which also corrects links in projects with a single `go.mod`. Re-index Go projects after upgrading. Thanks @GoDiao for the report and @danusha2345 for the fix. (#2322) - In Go, an exported type written without a package, like `Node` in `func Walk(v Visitor, node Node)`, now links to the type of that name in its own package, whether it is a struct, an interface or another kind of type. Before, it could link to a struct of the same name in an unrelated package, so prometheus's PromQL parser functions pointed at the Kubernetes discovery `Node` struct instead of the parser's own `Node` interface, or, for a result type like `Appender` in `func (f *fanout) Appender(…) Appender`, to the method itself. A type from a package outside your project, like `apiv1.Node` or `http.Handler`, no longer links to a struct of the same name in the file that uses it; a variadic parameter like `...storage.Filter` now links to that package's type; a route handler written as a method value, like `h.Follow` in an Echo or Gin app, links to that method rather than a model struct named `Follow`; and a method called on another call's result or on an indexed value, like `err[i].Error()` or a chained `.String()`, is no longer recorded as creating a struct of the same name. Re-index Go projects after upgrading. - In Go, a function or method passed as a value, like `sync.Pool{New: wm.new}`, now shows the function that passes it among its callers and impact even when it is named `new`, `nil`, `None`, `self` or a handful of similar words, or when a line break or comment sits beside the dot. Before, such a value was skipped, so a method used only that way looked unused. Re-index Go projects after upgrading. -- In Go, a type in a parameter, a result or a composite literal now always links to a type, never to a method or function that shares its name. Before, an unexported type like `keyIndex` in etcd's `func (ti *treeIndex) KeyIndex(keyi *keyIndex) *keyIndex` linked to the `keyIndex` method declared beside it, prometheus's `samples{…}` literals to a `samples` method, and a type from a package outside your project, like `semver.Version` or `config.URL`, to whatever project method or function had that name. A type written without a package, including an unexported one or one named like `AppenderV2` or `Histogram_CountInt`, now links to its own package's type when there is one, and a generic method's type parameter, like `T` in `func (p *Pool[T]) Get() T`, no longer links to an unrelated `T`. Re-index Go projects after upgrading. +- In Go, a type in a parameter, a result or a composite literal now always links to a type, never to a method or function that shares its name. Before, an unexported type like `keyIndex` in etcd's `func (ti *treeIndex) KeyIndex(keyi *keyIndex) *keyIndex` linked to the `keyIndex` method declared beside it, prometheus's `samples{…}` literals to a `samples` method, and a type from a package outside your project, like `config.URL` in `&config.URL{…}`, to whatever project method had that name. A type written without a package, including an unexported one or one named like `AppenderV2` or `Histogram_CountInt`, now links to its own package's type when there is one, and a generic method's type parameter, like `T` in `func (p *Pool[T]) Get() T`, no longer links to an unrelated `T`. Re-index Go projects after upgrading. - Indexing a project that includes large bundled JavaScript files, such as a copy of pdf.js or d3, is fast again: since 1.6.2, resolving the calls in a JavaScript or TypeScript file re-read the file's text above each call, so a single bundled library could add many seconds to an index. The graph it builds is unchanged. Thanks @bompus for the report. (#2334) - The time `codegraph init` and `codegraph index` print beside the node and edge counts now covers the whole run, resolving references and linking included, and `codegraph sync` reports its whole run the same way. Before, it counted only reading and parsing the files, which can be a small part of an index, so a slow index looked fast. Thanks @bompus for the report. (#2334) - In Rust, code that uses an enum only through its variants, like `mode::Mode::A` in an expression, a `Mode::B =>` match arm or `Self::A` inside the enum's own `impl`, now shows up in that enum's callers and impact, linked to the enum that actually declares the variant rather than to a same-named type elsewhere. Standard-library variants like `Ordering::Less` or `Option::Some` and associated items like `Foo::new()` or `Foo::MAX` don't count as using a project enum. Re-index Rust projects after upgrading. Thanks @mg-mg-mg for the report and @danusha2345. (#2328)