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
143 lines
4.2 KiB
Go
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
|
|
}
|