Repository navigation
fix(go): a type alias type A = B is indexed and links to the type it names - #2417
Merged
Merged
Conversation
…t names
tree-sitter-go parses `type Event = mvccpb.Event` as a `type_alias`, beside
the `type_spec` of a defined type, and both extractors read `type_spec`
only. etcd's client/v3 `Event` had no node; a use through its package
(`clientv3.Event`, prometheus's `storage.AOptions{…}`) linked to nothing,
and a use inside it linked by name to any type so named, another package's
included.
- Extraction (TS and kernel): an alias is a `type_alias` node referencing
each type its right-hand side names, at the name so resolution reads the
package back; an alias of a struct or interface literal is a struct or
interface. The alias's own type parameters and Go's predeclared types are
skipped. A generic alias, which tree-sitter-go 0.23 parses as a
`type_spec` around an error, reads as an alias on wasm; the kernel defers
its file.
- A method called on an alias-typed value is the aliased type's: an alias
of a project type is followed in its own package, through pointers and
further aliases; one of an outside type resolves by name as before.
- An alias's target written through a package that is none of the file's
imports as indexed stays unresolved, as an embedded type's does, so the
alias never links to itself or a namesake.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hich #2410 keeps unknown #2410 makes an unaliased go.yaml.in/yaml/v3 known as yaml, so the test's yaml.Node alias no longer reached the rule. An unaliased go.etcd.io/etcd/client/v3 is known as v3 or client, never clientv3; with the rule disabled, this case fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a-0b67b7 # Conflicts: # CHANGELOG.md
…a-0b67b7 # Conflicts: # src/resolution/index.ts
…a-0b67b7 # Conflicts: # CHANGELOG.md
…satisfy With #2419, goImplementsEdges offers every Go type_alias as an implementer. A true alias now has a node, and owns the methods written with it as the receiver (prometheus's func (c *KumaSDConfig) Name(), KumaSDConfig = SDConfig), which Go gives the aliased type; so the alias implements discovery.Config in SDConfig's place. The doc comment said a true alias is not extracted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
danusha2345
pushed a commit
to danusha2345/codegraph
that referenced
this pull request
Oct 7, 2026
…o guess for outside types Three receiver shapes still got no type, and a receiver typed outside the project still got a guess by method name: - A variable named like a standard-library package (`ring`, `token`, `parser`). Every `ring.X()` was skipped as a call into that package, whether or not the file imports it. It is skipped now only when the file imports the package; otherwise the receiver is typed like any other, and one whose type is unknown still gets no edge. - A parameter or receiver of a package-qualified type (`s *store.Store`). - A receiver bound to a call: `r := newRing()`, `s, err := store.NewStore()`, `if r := find(id); r != nil`, a call spread over several lines, a conversion (`list := model.BotList(bots)`). The type is the callee's first result as its signature spells it, read in the callee's file. Only when the call is the whole right-hand side, and not when it takes the receiver itself. A receiver whose declared type comes from a package outside the project's modules (`conn net.Conn`, `ctx context.Context`, `req *http.Request`, an alias of one, the result of an outside function) gets no edge: `ctx.Done()` went to the one project type declaring a `Done`. A project function's result declared as an outside type by value (`http.RoundTripper`) is left as it was, since that is usually an interface a project type implements. Aliases of project types are followed by colbymchenry#2417, which gave them nodes; the alias reader this change carried is gone, and an alias of an outside type is recognized from that node's declaration. Resolver only: no extraction change, the receiver scan keeps its per-line memo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 7, 2026
Open
colbymchenry
added a commit
that referenced
this pull request
Oct 7, 2026
…method (#2444) Both extractors record `srv.(KVServer).Range(ctx, in)` as a bare `Range` call at the column where its receiver expression starts, as they record any call through an expression. Resolution matched the name alone, so every handler protoc-gen-go-grpc emits went to the `UnimplementedKVServer` stub beside the `KVServer` interface (72 edges in etcd), and calls like `c.Reader.(*pipe).Close()`, `p.(Pausable).Pause()` or `v.(featuregate.MutableFeatureGate).Set(…)` linked to a namesake or to nothing. The call's chain is now read back from its column (operand, selectors, assertions, calls and indexes, past strings, runes and comments, and on to the next line after a trailing `.`). When the link of the call's name follows an assertion, the call is a method of the asserted type, found where Go finds it: - a bare `T` or `*T` in the call's own package, or one it dot-imports; - a `pkg.T` in the imported project package; - its own method, the one its interface declares, or one promoted from a type it embeds, through the package-scoped lookup #2361 added; an alias is followed to the type it names (#2417). A type from outside the project (`http.Flusher`), a predeclared one (`error`), an alias of an outside type (`type Ctx = context.Context`, for which the package-scoped lookup would fall back to matching by name) or a type literal (`interface{ Flush() }`) links nothing. The check runs ahead of the framework, import and name strategies, and ahead of the built-in filter, which dropped a method named `close` or `copy` called through an assertion. Two calls of one name in a chain share the column (`b.(*Builder).Add(1).Add(2)`); both are taken for the one made through the assertion, whose edge is there either way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
colbymchenry
added a commit
that referenced
this pull request
Oct 7, 2026
A Go defined type (`type WatchChan <-chan WatchResponse`, gin's `type HandlerFunc func(*Context)` and `type HandlersChain []HandlerFunc`) is a `type_alias` node, and it referenced nothing: only an `=` alias (#2417) named the types on its right-hand side. Impact on `WatchResponse` or gin's `Context` missed the declarations built from them, and everything that uses those. Fix (TS extractor and the Rust kernel, mirrored): goAliasTypeNames / alias_type_names walk the `type` field of every type_alias node, a defined type's as well as an alias's, so each type it names becomes a `references` ref on that name, where resolution reads the package qualifier back. Still skipped: the declaration's own type parameters, Go's predeclared types, and now its own name written bare, which in a recursive type (prometheus' `type stateFn func(*Lexer) stateFn`) is the declaration itself: no self-edge, and no failed row that would make dead code treat every namesake as referenced. A qualified name is never skipped (`type PutResponse pb.PutResponse` names pb's). Resolution is unchanged. A defined type is a new type: goAliasTarget still follows `=` aliases only, so a method called on a defined type is not looked up on its underlying type. The gateTargetKind rule that leaves a type_alias's reference written through a package the index doesn't know unresolved now covers defined types too; without it, every such name bound to the same-file declaration of its name (`type Op clientv3.Op` linked itself). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
danusha2345
pushed a commit
to danusha2345/codegraph
that referenced
this pull request
Oct 7, 2026
…o guess for outside types Three receiver shapes still got no type, and a receiver typed outside the project still got a guess by method name: - A variable named like a standard-library package (`ring`, `token`, `parser`). Every `ring.X()` was skipped as a call into that package, whether or not the file imports it. It is skipped now only when the file imports the package; otherwise the receiver is typed like any other, and one whose type is unknown still gets no edge. - A parameter or receiver of a package-qualified type (`s *store.Store`). - A receiver bound to a call: `r := newRing()`, `s, err := store.NewStore()`, `if r := find(id); r != nil`, a call spread over several lines, a conversion (`list := model.BotList(bots)`). The type is the callee's first result as its signature spells it, read in the callee's file. Only when the call is the whole right-hand side, and not when it takes the receiver itself. A receiver whose declared type comes from a package outside the project's modules (`conn net.Conn`, `ctx context.Context`, `req *http.Request`, an alias of one, the result of an outside function) gets no edge: `ctx.Done()` went to the one project type declaring a `Done`. A project function's result declared as an outside type by value (`http.RoundTripper`) is left as it was, since that is usually an interface a project type implements. Aliases of project types are followed by colbymchenry#2417, which gave them nodes; the alias reader this change carried is gone, and an alias of an outside type is recognized from that node's declaration. Resolver only: no extraction change, the receiver scan keeps its per-line memo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A Go alias declaration (
type Event = mvccpb.Event,type ( A = B )) was in neither extractor's graph. tree-sitter-go parses it as atype_aliasnode beside thetype_specof a defined type, and both the wasm extractor (languages/go.tslisted onlytypeAliasTypes: ['type_spec']) and the kernel (go.rsmatched onlytype_spec) readtype_specalone. So etcd'sclient/v3/watch.go:52 type Event = mvccpb.Eventhad no node while line 54'stype WatchChan <-chan WatchResponsedid.What that did downstream, measured on main (fe95183, and again on 31c3328):
clientv3.Event, prometheus's 390storage.AOptions{…}literals) linked to nothing.&PrometheusVersion{}inweb/went toweb/api/testhelpers's struct, and[]Typeinutil/compressiontotsdb/compression'sType.type Items[T any] = List[T]) has no rule in tree-sitter-go 0.23. It parses as atype_specaround anERRORfor the=, so the kernel defers the file and wasm's error recovery already made atype_aliasnode. That node named nothing, and method resolution then stopped at it:xs.Len()on an*Items[int]resolved to nothing.Fix
Extraction (TS and kernel, kept at parity)
typeAliasTypesis['type_spec', 'type_alias']. The kernel's walker routestype_aliasthe same way.struct/interfacenode, exactly as atype_specdoes.type_aliasnode with areferencesref for each type its right-hand side names. Each ref sits on the name, so resolution reads a package qualifier back (Go: cross-package calls are not resolved whengo.modis not at the project root #2322). The alias's own type parameters and Go's predeclared types are skipped.goAliasTypeNames(go.ts) andalias_type_names(go.rs) also recognize the generic-alias error shape on the wasm side. The kernel never sees it, since that file defers.Resolution
resolveGoMethodInPackagetreats an alias as the type it names. A method called on an*Eventis looked up onmvccpb.Event, through any chain of aliases and pointers (type Ptr = *Local,type Local = WatchResponse).type Ctx = context.Context,func(…),map[…]…) resolves exactly as before, by name. Defined types (type Dur int) are not followed.gateTargetKind: an alias's target written through a package that is none of the file's imports as indexed (clientv3under an unaliasedgo.etcd.io/etcd/client/v3, which the index knows asv3or, since fix(go): an unaliased import is also known by the name goimports assumes for its package #2410,client) stays unresolved. This is the rule fix(go): an interface links the interfaces it embeds #2397 applies to embedded types. Without it, name matching linked the alias to itself or to a namesake.Validation
Tests:
__tests__/go-type-alias.test.ts(14 tests).Event{}/*Eventreaching the alias, method calls through one, two and generic aliases, and an alias written through an import the index doesn't know. That last case fails with the rule disabled.api/, so on main the call through*Eventwent to itsEvent::IsCreate.torture.gogains every alias shape;kernel-tsjs-parity(92 tests) passes.wm.neworraft.Nonekeeps its function ref #2401, fix(sync): a Liquid section or snippet reference links its file when the file appears later #2403 and fix(cpp): a base class links to the class C++ name lookup finds, not a namesake #2407 landed meanwhile, fix(kernel): a Go value likewm.neworraft.Nonekeeps its function ref #2401 editinggo.rs), the kernel was rebuilt from the merged source. Then 16 files, 926 tests, pass: these tests, every Go test file,kernel-tsjs-parity,kernel-grammar-parity,kernel-node-id-parity, and the files main's new commits touched (extraction,cpp-base-class-lookup,sync-path-reference-retry).go.yaml.in/yaml/v3known asyaml, so the test's unknown-package case moved toclientv3, the shape fix(go): an unaliased import is also known by the name goimports assumes for its package #2410 keeps unknown; test(sync): compare synced graphs with an index rebuilt from scratch #2411), 14 files, 251 tests, pass. These are these tests,go-import-package-names,go-interface-embedding,sync,sync-import-retry,kernel-tsjs-parityand the other Go test files.go-promoted-dispatch,dead-code,cpp-included-test-suite,react-router,ts-ambient-exports,js-local-binding-backtracking,jsx-render-workandkernel-tsjs-parity.import { default as X }is the module's default import #2412, fix(frameworks): a framework resolves only references written in its own languages #2415, fix(sync): a route links the module it lazily loads when the module appears later #2422, fix(cpp): a class keeps its namespaces and enclosing class past code the parser misreads #2426) and at d348d30 (fix(go): a type in a signature or composite literal links a type, never a method of its name #2416, fix(sync): a navigation call links its route when the route appears later #2425).gateTargetKind. The alias rule now runs first: that gate returns early for every Go type position, and it keeps a qualified ref that landed on any type even when the qualifier is unknown.go-type-position-kinds,framework-resolve-languageandsync-navigation-retry.go-type-aliasgains aKumaSDConfig-shaped case, alias methods implementing an interface, and 14 files, 185 tests, pass, includinggo-implements-defined-types(its true-alias case still holds: that alias owns no methods).ref.language === 'cpp'). 10 files, 172 tests, pass, includingcpp-library-type-receiver.Full suite (before the merge, on fe95183 + this change): 26 of 6496 failed in the parallel run on this shared Windows box. All were daemon / MCP / sync / watcher / perf-threshold tests: hook timeouts,
EBUSYon teardown, and two timing bounds (2341 ms vs 2000, 101.7 ms vs 100).mcp-daemon(21/21) andmpeg-ts-not-typescript(11/11) pass alone.ref.language === 'go'.Kernel parity sweep (
scripts/kernel-parity.mjs --lang go), main 31c3328 vs this branch:The wasm totals move by exactly the aliases: etcd +13 nodes / +26 refs, prometheus +9 / +8. (Before #2401 landed, the sweep showed its 6 / 202
raft.None/compression.Nonediffs, the same count in both arms.)A/B on real repos (shallow clones: etcd 76d58e3, prometheus 39c878f, gin 43fe48e). Each repo was indexed with real builds of main 31c3328 and of this branch, on the kernel path and, for etcd and prometheus, the wasm path too. Kernel and wasm graph dumps are byte-identical in each arm, and the diffs are the same on both paths, and the same as the first A/B against fe95183. Re-run against each later main, the delta was identical edge for edge every time, with no dispatch or implements edge changed: d8a7f86 (after #2410), 855eca2 (after #2414's promoted-method dispatch), 9d07864 (after #2415) and d348d30 (after #2416). Only the baselines moved: at d348d30, etcd went 63105 → 63215 and prometheus 96813 → 97294. Against 2f2afea (after #2419), prometheus adds the 4 implements/dispatch edges described below (+485), and etcd and gin are unchanged. The table shows the 31c3328 run.
Edges are diffed site by site (source, line, column, ref name), and each target is judged by Go's scoping: a bare name belongs to its own package,
pkg.Xto the import's package.containscontains+ 5go-method-containsEvery retargeted and added edge was checked:
&Event{×2 inclient/v3/watch_test.go, frommvccpb's struct;&PrometheusVersion{}×7 inweb/web_test.go, fromweb/api/testhelpers's struct;[]Type×3 inutil/compression, fromtsdb/compression'sType.clientv3.Event,pb.Watch_WatchServer,storage.AOptions{…},compression.Type);KeyPredicate,recCase, the test files'sample);Event → mvccpb.Event, and the grpc stream aliases → their request/response structs, whilegrpc.ServerStreamingClientstays unresolved;pb.KV_RangeStreamServer.go-method-contains(5): prometheus'sfunc (c *KumaSDConfig) Name()and four more are written on the aliasKumaSDConfig = SDConfig. The existing synthesizer already acceptstype_aliasowners, the same path that gives a defined type its methods, so these five methods, orphaned before, now hang off the alias.type_alias), the alias that owns those methods implements what they satisfy, standing in forSDConfig. On prometheus that is 1go-implementsedge,KumaSDConfig → discovery.Config, whose methods areName,NewDiscovererandNewDiscovererMetrics, and 3interface-impledges, so a call throughdiscovery.Confignow reaches the Kuma config's methods. An alias without methods of its own, the rest of them, implements nothing. ThegoImplementsEdgesdoc comment said a true alias is not extracted, so it now says what the alias owns.p.Config.RevisionOffsetRange.Rand()still callsRange::Rand, at 0.7 instead of 0.9, because a secondRand(the antithesis package'stype Rand = mrand.Rand) now exists.type_aliasrows of the dead-code report are unchanged on etcd and prometheus, with exported, test and generated code included.The same module also reproduces the issue on the built CLI. On main,
isCreate'se.IsCreate()andnewEvent'sEvent{}go to the namesake package, andp.Err()(through two aliases) andxs.Len()(generic alias) resolve to nothing. This branch gives the alias nodes,Event → mvccpb.Event, and the calls onmvccpb.Event,WatchResponseandList.Overlap
#1954 (open, @danusha2345) also teaches
resolveGoMethodInPackageabout aliases, readingtype Name = Targetfrom the package's files when the package has no node of that name. Once this lands, aliases have nodes, so that branch no longer fires and the alias-following here covers it. #1954's other parts are untouched by this PR:Here, an alias of an outside type still resolves by name, as on main, which leaves #1954's "outside types get no edge" rule to it.
Not in this PR
type WatchChan <-chan WatchResponse, gin'stype HandlerFunc func(*Context)) still references nothing: before this PR no Go type declaration had areferencesedge to the type it is defined from. Aliases now do. Doing the same for defined types is a separate, larger graph change.// Foo …thentype Foo struct{}) is dropped by both extractors; only types inside atype ( … )group keep theirs. Aliases behave like other types here.func (c *KumaSDConfig) Name()withKumaSDConfig = SDConfig) belong toSDConfig's method set in Go. The graph keeps them under the alias, which implementsdiscovery.ConfiginSDConfig's place. An interface whose methods are split between the two (some written onSDConfig, some on the alias) is satisfied by neither node.🤖 Generated with Claude Code