Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions internal/semantic/lsp/enrich_confirm.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,46 @@ type enrichTarget struct {
edge *graph.Edge
}

// confirmableEdgeKind reports whether the reference-confirm pass and its
// definition-rebind fallback can meaningfully adjudicate an edge kind.
//
// The pass matches an edge's SITE line against the reference list of its target
// declaration, and on a miss asks the server what the site resolves to and
// rebinds. That is meaningful for every edge that anchors a use / reference /
// flow site the server can resolve:
//
// - use sites: calls, references, field reads/writes, type instantiations;
// - type positions: typed_as / returns — when a concrete impl and the
// interface it satisfies share a name the resolver may pick the wrong one,
// and the server's definition rebinds to the declared type (measured: it
// binds a param / return typed as an interface to the interface, not an
// implementation);
// - dataflow through a call: arg_of / value_flow / returns_to — the server
// disambiguates the callee the value flows into (measured: it rebinds an
// arg_of from the wrong same-named method onto the one the call actually
// resolves to, e.g. Pool.EnqueueJob rather than Client.EnqueueJob).
//
// It is NOT meaningful for STRUCTURAL CONTAINMENT — a declaration's fixed
// relationship to the structure that encloses it: a method to its type
// (member_of), a symbol to its definer (defines) or container (contains), a
// param to its function (param_of), a file to an imported package (imports), a
// closure to a captured variable (captures). These are AST-deterministic and
// carry no distinct use site, so the reference match wastes a round-trip and,
// on a miss, can feed a correct edge into the definition-rebind fallback and
// mutate its target (observed: a local variable's member_of edge rebound onto
// an unrelated same-named method). member_of alone is the single largest
// confidence<1.0 kind, so skipping this family removes a large slice of the
// confirm round-trips while, if anything, improving correctness.
func confirmableEdgeKind(k graph.EdgeKind) bool {
switch k {
case graph.EdgeMemberOf, graph.EdgeDefines, graph.EdgeContains,
graph.EdgeParamOf, graph.EdgeImports, graph.EdgeCaptures:
return false
default:
return true
}
}

// confirmGroup is the confirm pass's per-file work unit: every ambiguous
// target whose referent declaration lives in rel. Grouping lets one didOpen of
// rel serve all its targets' reference queries.
Expand Down
39 changes: 35 additions & 4 deletions internal/semantic/lsp/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,15 @@ func (p *Provider) EnrichRepoContext(ctx context.Context, g graph.Store, repoPre
if e.Confidence >= 1.0 {
continue
}
// Skip structural-containment edges (member_of, defines, contains,
// param_of, imports, captures): they anchor no use site a reference
// lookup can adjudicate, so confirming them wastes a round-trip and can
// feed a correct edge into the definition-rebind fallback and mutate its
// target. Use-site, type-position and dataflow edges stay confirmable.
// See confirmableEdgeKind.
if !confirmableEdgeKind(e.Kind) {
continue
}
if from, ok := langAllByID[e.From]; ok {
targets = append(targets, enrichTarget{node: from, edge: e})
}
Expand Down Expand Up @@ -904,6 +913,23 @@ func (p *Provider) EnrichRepoContext(ctx context.Context, g graph.Store, repoPre
return
}
defer release()
// findReferences is positioned on the TARGET declaration, not the
// call site, so every ambiguous edge in this group that points at
// the same referent asks the server for the identical reference
// list — measured ~9x fan-in for calls, ~8x for references, since
// an overloaded / hot declaration draws many candidate edges.
// confirmRefMatchesSite still filters that shared list per edge,
// so caching it by target node turns N identical round-trips into
// one with byte-identical confirm/refute decisions. A target's
// file is unique to one group, so every edge to it lands here —
// the per-group cache captures all of its redundancy. ok=false
// records a server error so repeat targets skip exactly as the
// un-cached path did (error → skip, no fallback).
type cachedRefs struct {
refs []Location
ok bool
}
refsByTarget := make(map[string]cachedRefs, len(grp.targets))
for _, t := range grp.targets {
if targetedCtx.Err() != nil {
return
Expand All @@ -916,12 +942,17 @@ func (p *Provider) EnrichRepoContext(ctx context.Context, g graph.Store, repoPre
if !ok {
continue
}
col := identifierColumn(content, toNode.StartLine, toNode.Name)
refs, err := p.findReferences(absRoot, grp.rel, line, col)
if err != nil {
cr, seen := refsByTarget[t.edge.To]
if !seen {
col := identifierColumn(content, toNode.StartLine, toNode.Name)
refs, err := p.findReferences(absRoot, grp.rel, line, col)
cr = cachedRefs{refs: refs, ok: err == nil}
refsByTarget[t.edge.To] = cr
}
if !cr.ok {
continue
}
if p.confirmRefMatchesSite(refs, absRoot, repoPrefix, t) {
if p.confirmRefMatchesSite(cr.refs, absRoot, repoPrefix, t) {
rmu.Lock()
semantic.ConfirmEdge(t.edge, p.Name())
semantic.PersistEdge(g, t.edge)
Expand Down
Loading