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
+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,