From c3f621d4bcc2ab905b5ac95ee270b8a9ae67f96e Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Tue, 6 Oct 2026 20:20:15 -0500 Subject: [PATCH 1/2] fix(kernel): a Go value like `wm.new` or `raft.None` keeps its function ref The kernel's Go fn-ref capture checked a selector value's field name against the name stoplist, so `raft.None`, `compression.None`, cgo's `C.NULL` and a method value like `sync.Pool{New: wm.new}` were dropped. The wasm extractor, the reference, applies NAME_STOPLIST to the normalized name (`raft.None`), which the stoplist never matches. It also builds that name from the operand and the field, so a line break or comment beside the dot keeps the candidate; the kernel validated the selector's whole source text and dropped it. Build the name from operand and field as normalizeSpecial does, and drop the field-name check. No other walker has the pattern: each applies the stoplist to the same name as its TS spec. kernel-parity.mjs --lang go: prometheus 202 -> 0 function_ref diffs, etcd 6 -> 0, golang/go 35 -> 0, kapacitor 2 -> 0, plus 0 on gin, grpc-go, hugo, caddy and go-ethereum. Indexed with both kernels, edges are unchanged on prometheus, etcd, gin and hugo; kapacitor gains the one missing edge, NewExecutor -> workerMaker::new. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + __tests__/kernel-tsjs-parity.test.ts | 24 ++++++++++++++++++++++++ codegraph-kernel/src/go.rs | 24 +++++++++++++----------- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6c0e71160..f71d53ffd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - In Dart, a type is now linked from more of the places it is written, not only from parameters and return types: the type an `extension … on` targets, a field's type, a top-level variable's type, generic arguments such as `Future.value(null)` or Riverpod's `final reportProvider = Family()`, and a local variable's type, a cast or a type check inside a function. So `callers` and impact for a model class now reach the extensions, models and providers that use it. Thanks @mg-mg-mg for the report. (#2327) - In Go, calls through a method receiver or parameter of an unexported type, the usual shape of gRPC and HTTP handlers (`s.service.AddItem()` inside `func (s *server) Create()`), now resolve, and always within that type's own package: another package's `server` with a same-named method no longer takes the call, and a method the type gets from a struct or interface it embeds is found too. Re-index Go projects after upgrading. Thanks @GoDiao for the report and the fix. (#2323) - 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, 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. - 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__/kernel-tsjs-parity.test.ts b/__tests__/kernel-tsjs-parity.test.ts index 866d80d30b..57fec8649d 100644 --- a/__tests__/kernel-tsjs-parity.test.ts +++ b/__tests__/kernel-tsjs-parity.test.ts @@ -383,6 +383,30 @@ class Consumer: expect(result.unresolvedReferences.some(r => r.referenceKind === 'calls' && r.referenceName === 'c.Fetch')).toBe(true); }); + it.each(['LF', 'CRLF'])('Go selector values stoplist the whole name, not the field (%s)', (ending) => { + // The stoplist holds bare words like Python's `None`; `raft.None` (etcd) + // and cgo's `C.NULL` are still values. The name is operand + field, so a + // break or comment after the dot keeps it; one inside the operand doesn't. + const source = `package demo +import "example.com/raft" +type Store struct{} +func (s *Store) new() any { return nil } +func wire(s *Store) { + Submit(raft.None, C.NULL, None, nil) + pool := sync.Pool{New: s.new} + Submit(raft. + Next, raft./* c */ Prev, raft.// c + Last) + Submit(s. + inner.Fetch) + _ = pool +} +`; + const result = assertParity('values.go', ending === 'CRLF' ? source.replace(/\n/g, '\r\n') : source, 'go'); + const names = result.unresolvedReferences.filter(r => r.referenceKind === 'function_ref').map(r => r.referenceName); + expect(names.sort()).toEqual(['C.NULL', 'raft.Last', 'raft.Next', 'raft.None', 'raft.Prev', 's.new']); + }); + it.each(REAL_SOURCES)('real source parity: %s', (rel) => { const file = path.join(__dirname, '..', rel); assertParity(rel, fs.readFileSync(file, 'utf8'), 'typescript'); diff --git a/codegraph-kernel/src/go.rs b/codegraph-kernel/src/go.rs index 239c788a6e..d3da7dc328 100644 --- a/codegraph-kernel/src/go.rs +++ b/codegraph-kernel/src/go.rs @@ -1009,18 +1009,20 @@ impl<'t> Walker<'t> { row: p.row, }); } - // #1820: preserve the receiver of a method value. + // #1820: preserve the receiver of a method value. The name is + // rebuilt from operand and field, as normalizeSpecial does, so a + // line break or comment after the dot keeps the candidate. + // NAME_STOPLIST applies to the whole name and holds no dotted + // word, so it never drops one: `raft.None` is a candidate even + // though a bare `None` is not. "selector_expression" => { - let field = v - .child_by_field_name("field") - .or_else(|| v.named_child(v.named_child_count().saturating_sub(1))); - let Some(field) = field else { return }; - let name = self.text(field); - if name.is_empty() || is_stoplisted(name) { + let (Some(operand), Some(field)) = + (v.child_by_field_name("operand"), v.child_by_field_name("field")) + else { return; - } - let value = self.text(v); - if !value.split('.').all(|part| { + }; + let name = format!("{}.{}", self.text(operand), self.text(field)); + if !name.split('.').all(|part| { !part.is_empty() && part.chars().enumerate().all(|(i, c)| { c == '_' || c.is_ascii_alphabetic() || (i > 0 && c.is_ascii_digit()) }) @@ -1028,7 +1030,7 @@ impl<'t> Walker<'t> { let p = field.start_position(); self.fn_ref_cands.push(Cand { from, - name: value.to_string(), + name, line: p.row as u32 + 1, column_byte: field.start_byte(), row: p.row, From fe0a2487e93155370f4fb5c861ff5edd048cefe9 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Tue, 6 Oct 2026 20:25:44 -0500 Subject: [PATCH 2/2] fix(kernel): note that a comment before the dot keeps a Go selector value too Co-Authored-By: Claude Opus 5.5 --- codegraph-kernel/src/go.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegraph-kernel/src/go.rs b/codegraph-kernel/src/go.rs index 0674d9357f..3a93bacf19 100644 --- a/codegraph-kernel/src/go.rs +++ b/codegraph-kernel/src/go.rs @@ -1030,7 +1030,7 @@ impl<'t> Walker<'t> { } // #1820: preserve the receiver of a method value. The name is // rebuilt from operand and field, as normalizeSpecial does, so a - // line break or comment after the dot keeps the candidate. + // comment or line break beside the dot keeps the candidate. // NAME_STOPLIST applies to the whole name and holds no dotted // word, so it never drops one: `raft.None` is a candidate even // though a bare `None` is not.