Files
zzet--gortex/internal/resolver/generic_param_bind.go
T
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

143 lines
4.2 KiB
Go

package resolver
import (
"strings"
"github.com/zzet/gortex/internal/graph"
)
// bindGenericParamRefs rewrites `unresolved::<name>` edges where the
// name is a generic type parameter declared by the source's
// enclosing function. The Go extractor already materialises
// KindGenericParam nodes with IDs `<func>#tparam:<name>` and an
// EdgeMemberOf back to the owner — the resolver just hasn't been
// consulting them when an in-body reference (`var x T`, return type
// `T`, etc.) lands as `unresolved::T`.
//
// Side benefit beyond stub reduction: `find_usages` on a generic
// type parameter starts working — *"where in this generic function
// is T used?"* — which is a real refactoring query.
//
// Scope is per-function: a function's tparams are visible only
// inside its body. The owner-keyed index built here lets each edge
// resolve in O(1) without re-walking the graph.
func (r *Resolver) bindGenericParamRefs() {
// owner-function ID → set of tparam-name → tparam-node-id.
owned := map[string]map[string]string{}
for n := range r.graph.NodesByKind(graph.KindGenericParam) {
if n.Language != "go" || n.Name == "" {
continue
}
owner := enclosingFunctionForBinding(n.ID)
if owner == "" || owner == n.ID {
continue
}
set, ok := owned[owner]
if !ok {
set = map[string]string{}
owned[owner] = set
}
// Don't overwrite — two tparams with the same name in the
// same function shouldn't happen in valid Go, but be defensive.
if _, dup := set[n.Name]; dup {
set[n.Name] = ""
continue
}
set[n.Name] = n.ID
}
if len(owned) == 0 {
return
}
var batch []graph.EdgeReindex
// We don't know up front which edge kinds carry type-param refs:
// EdgeReferences for `var x T`, EdgeTypedAs for parameters typed
// as T, EdgeReturns for return signature, EdgeInstantiates for
// generic instantiation expressions. Walk the union.
for _, k := range []graph.EdgeKind{
graph.EdgeReferences,
graph.EdgeTypedAs,
graph.EdgeReturns,
graph.EdgeInstantiates,
} {
for e := range r.graph.EdgesByKind(k) {
if old := r.tryBindGenericParam(e, owned); old != "" {
batch = append(batch, graph.EdgeReindex{Edge: e, OldTo: old})
}
}
}
if len(batch) > 0 {
r.graph.ReindexEdges(batch)
}
}
// bindGenericParamRefsForFile is the single-file-resolve form of
// bindGenericParamRefs. A type parameter is visible only inside its own
// function's body, which lives in this file, so both the owner index and the
// edges to rewrite are scoped to the file — no whole-graph EdgesByKind sweep
// (the dominant cost of an incremental edit on a large graph).
func (r *Resolver) bindGenericParamRefsForFile(filePath string) {
owned := map[string]map[string]string{}
for _, n := range r.graph.GetFileNodes(filePath) {
if n == nil || n.Kind != graph.KindGenericParam || n.Language != "go" || n.Name == "" {
continue
}
owner := enclosingFunctionForBinding(n.ID)
if owner == "" || owner == n.ID {
continue
}
set, ok := owned[owner]
if !ok {
set = map[string]string{}
owned[owner] = set
}
if _, dup := set[n.Name]; dup {
set[n.Name] = ""
continue
}
set[n.Name] = n.ID
}
if len(owned) == 0 {
return
}
var batch []graph.EdgeReindex
for _, e := range r.fileOutEdges(filePath) {
switch e.Kind {
case graph.EdgeReferences, graph.EdgeTypedAs, graph.EdgeReturns, graph.EdgeInstantiates:
if old := r.tryBindGenericParam(e, owned); old != "" {
batch = append(batch, graph.EdgeReindex{Edge: e, OldTo: old})
}
}
}
if len(batch) > 0 {
r.graph.ReindexEdges(batch)
}
}
// tryBindGenericParam returns the old To value (for batched reindex)
// when the edge was rewritten, or "" when left alone.
func (r *Resolver) tryBindGenericParam(e *graph.Edge, owned map[string]map[string]string) string {
if e == nil || !strings.HasPrefix(e.To, "unresolved::") {
return ""
}
name := strings.TrimPrefix(e.To, "unresolved::")
if name == "" || strings.ContainsAny(name, ".*:#") {
return ""
}
ownerID := enclosingFunctionForBinding(e.From)
if ownerID == "" {
return ""
}
set := owned[ownerID]
if len(set) == 0 {
return ""
}
target, ok := set[name]
if !ok || target == "" || target == e.To {
return ""
}
oldTo := e.To
e.To = target
return oldTo
}