fix(kernel): a Go value like wm.new or raft.None keeps its function ref (#2401)

* 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 <noreply@anthropic.com>

* fix(kernel): note that a comment before the dot keeps a Go selector value too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-07 03:08:27 +00:00
committed by GitHub
parent 4040eab975
commit 5ace306b7c
3 changed files with 38 additions and 11 deletions
+1
View File
@@ -36,6 +36,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- 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, 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.
- 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)
+24
View File
@@ -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');
+13 -11
View File
@@ -1028,18 +1028,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
// 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.
"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())
})
@@ -1047,7 +1049,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,