From 18030d6ea72d89adc3ef030885a7227ad0eac515 Mon Sep 17 00:00:00 2001 From: "Tapabrata \"Topo\" Pal" Date: Sun, 11 Oct 2026 22:00:32 -0400 Subject: [PATCH 1/2] java: declare anonymous classes and lambdas in field initializers and initializer blocks gson TypeAdapters.java declares 25 of its 31 TypeAdapter.write overrides as anonymous classes assigned to static fields (CLASS = new TypeAdapter<>() { ...}). The Java visitor only looked for anonymous classes and lambdas inside method bodies, so none of them was a symbol, and grove's change_impact reported the family closed at 6 of 31 (2026-10-12). Field declarators, `static { }` blocks and instance initializer blocks now go through the same nested-executable walk as method bodies. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01KXa6Cv1ZUXcdMqXcJfQFhX --- strategies/extractors.go | 13 +++++++++++++ strategies/metadata_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/strategies/extractors.go b/strategies/extractors.go index 44faae0..69977f6 100644 --- a/strategies/extractors.go +++ b/strategies/extractors.go @@ -1908,6 +1908,12 @@ func javaVisit(node *sitter.Node, filePath, blobSHA string, src []byte, imports javaMethodDecl(n, astkit.KindMethod, filePath, blobSHA, src, imports, parentClass, out) case "enum_body_declarations": javaVisit(n, filePath, blobSHA, src, imports, parentClass, out) + case "static_initializer", "block": + // `static { ... }` and instance initializer blocks: anonymous + // classes and lambdas declared inside them belong to the class. + if parentClass != "" { + javaNestedExecutables(n, filePath, blobSHA, src, imports, parentClass, out) + } case "enum_constant": if parentClass == "" { continue @@ -2277,6 +2283,13 @@ func javaFieldDecl(n *sitter.Node, filePath, blobSHA string, src []byte, imports Modifiers: modifiers, Annotations: javaAnnotations(n, src), }) + // An initializer declares code too: `CLASS = new TypeAdapter<>() { + // write(...) {...} }` (gson TypeAdapters has 25 such adapters; none + // was indexed, so change_impact on TypeAdapter.write reported the + // family closed at 6 of 31 overrides, 2026-10-12). Lambdas likewise. + // Walk the declarator, not its value: the value node is often the + // anonymous class or lambda itself, and the walker inspects children. + javaNestedExecutables(decl, filePath, blobSHA, src, imports, parentClass, out) } } diff --git a/strategies/metadata_test.go b/strategies/metadata_test.go index ec7384d..817af90 100644 --- a/strategies/metadata_test.go +++ b/strategies/metadata_test.go @@ -1412,3 +1412,39 @@ public class Outer2 { } } } + +// gson TypeAdapters.java (2026-10-12): 25 of its 31 TypeAdapter.write +// overrides live in anonymous classes assigned to static fields, and none +// was indexed. Anonymous classes and lambdas in field initializers and +// initializer blocks are declarations of the enclosing class. +func TestJava_AnonymousClassesInFieldInitializersAndBlocks(t *testing.T) { + src := `class Adapters { + static final Adapter CLASS = new Adapter() { + @Override public void write(Out out, Object v) { out.nullValue(); } + }; + final Runnable r = () -> { run(); }; + static { + REGISTRY.add(new Adapter() { public void write(Out out, Object v) {} }); + } + { helper = new Adapter() { public void write(Out out, Object v) {} }; } + abstract static class Adapter { public abstract void write(Out out, Object v); } +}` + syms, _ := extract(t, astkit.LangJava, src) + var anon, writes, lambdas int + for _, s := range syms { + switch { + case s.Kind == astkit.KindClass && strings.HasPrefix(s.Name, " Date: Sun, 11 Oct 2026 22:14:22 -0400 Subject: [PATCH 2/2] java: declare anonymous classes that are lambda bodies or enum-constant arguments A census against javac's declarations (grove: compiler-declared symbols) found the same walker gap in two more places: `url -> new Monitor() {...}` (dubbo MonitorFilterTest), `() -> new SubTypeToken() {}` (gson, commons-lang TypeLiteralTest), where the anonymous class IS the lambda's expression body, and `TWEETS(new TypeToken>() {})` (gson ParseBenchmark), an enum constant's constructor argument. The walker only inspected children, so neither was declared. Lambdas now walk their own node; enum constants walk their arguments. With this, javac declares nothing the parser lacks on gson, commons-lang or dubbo. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KXa6Cv1ZUXcdMqXcJfQFhX --- strategies/extractors.go | 12 +++++++++++- strategies/metadata_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/strategies/extractors.go b/strategies/extractors.go index 69977f6..fc22572 100644 --- a/strategies/extractors.go +++ b/strategies/extractors.go @@ -1919,6 +1919,12 @@ func javaVisit(node *sitter.Node, filePath, blobSHA string, src []byte, imports continue } javaEnumConstantDecl(n, src, parentClass, out) + // `TWEETS(new TypeToken>() {})`: anonymous classes + // and lambdas passed as constructor arguments are declarations of + // the enum (gson ParseBenchmark, 2026-10-12). + if args := n.ChildByFieldName("arguments"); args != nil { + javaNestedExecutables(args, filePath, blobSHA, src, imports, parentClass, out) + } if body := n.ChildByFieldName("body"); body != nil { javaVisit(body, filePath, blobSHA, src, imports, parentClass, out) } @@ -2251,7 +2257,11 @@ func javaLambdaDecl(n *sitter.Node, filePath, blobSHA string, src []byte, import Span: internalast.NodeSpan(n), Body: n.Content(src), Modifiers: []string{"lambda"}, CallSites: javaCallSites(body, src), }) - javaNestedExecutables(body, filePath, blobSHA, src, imports, parentClass, out) + // Walk the lambda node, not its body: an expression body can itself be + // the anonymous class or lambda (`url -> new Monitor() {...}`, dubbo; + // `() -> new SubTypeToken() {}`, gson), and the walker only + // inspects children (2026-10-12). + javaNestedExecutables(n, filePath, blobSHA, src, imports, parentClass, out) } func javaFieldDecl(n *sitter.Node, filePath, blobSHA string, src []byte, imports []string, parentClass string, out *[]astkit.Symbol) { diff --git a/strategies/metadata_test.go b/strategies/metadata_test.go index 817af90..d691a82 100644 --- a/strategies/metadata_test.go +++ b/strategies/metadata_test.go @@ -1448,3 +1448,34 @@ func TestJava_AnonymousClassesInFieldInitializersAndBlocks(t *testing.T) { t.Errorf("anonymous=%d write overrides=%d lambdas=%d; want 3, 3, 1", anon, writes, lambdas) } } + +// dubbo MonitorFilterTest / gson TypeTokenTest / gson ParseBenchmark +// (2026-10-12): an anonymous class that IS a lambda's expression body, or an +// enum constant's constructor argument, was never declared; javac found them. +func TestJava_AnonymousClassesAsLambdaBodiesAndEnumArguments(t *testing.T) { + src := `class Holder { + Factory f = url -> new Monitor() { + public String getUrl() { return url; } + }; + void m() { run(() -> new Token() {}); } + enum Api { + TWEETS(new Token() {}, x -> x); + Api(Token t, Fn f) {} + } +}` + syms, _ := extract(t, astkit.LangJava, src) + var anon, getURL, lambdas int + for _, s := range syms { + switch { + case s.Kind == astkit.KindClass && strings.HasPrefix(s.Name, "