Files
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

264 lines
8.7 KiB
Go

package graph
import "strings"
// Stub-node identifier conventions.
//
// A "stub" is a placeholder Node the resolver materialises for a
// symbol the indexer can see referenced but not defined in the
// current repo's source: a stdlib call, a language builtin, an
// external module import, etc. Stubs let the graph hold edges
// to "external" targets uniformly with edges to first-party
// nodes.
//
// Format (all stubs):
//
// <repoPrefix>::<kind>::<rest>
//
// where:
//
// repoPrefix — the owning repo's RepoPrefix (Indexer.RepoPrefix).
// Empty only when the stub is created outside a
// per-repo context (legacy single-repo daemons).
// kind — one of: stdlib, builtin, external_call, module.
// rest — kind-specific (e.g. "fmt::Errorf" for stdlib).
//
// Why per-repo? Two repos pinned to different language SDK
// versions have semantically distinct stdlib symbols. Go 1.21's
// `min` is a builtin; in 1.20 it isn't. A global `builtin::go::min`
// node would conflate them and produce wrong cross-repo edges.
// Per-repo prefix keeps them as distinct nodes; a future
// "same-as" edge can union them when the workspace knows the
// versions actually match.
const (
StubKindStdlib = "stdlib"
StubKindBuiltin = "builtin"
StubKindExternalCall = "external_call"
StubKindModule = "module"
)
// StubID composes a stub identifier with the per-repo prefix.
// Pass repoPrefix = "" when the caller is outside a per-repo
// context (single-repo daemons that haven't set a prefix).
func StubID(repoPrefix, kind string, parts ...string) string {
var b strings.Builder
if repoPrefix != "" {
b.WriteString(repoPrefix)
b.WriteString("::")
}
b.WriteString(kind)
for _, p := range parts {
b.WriteString("::")
b.WriteString(p)
}
return b.String()
}
// IsStub reports whether id is any stub kind. Cheaper than
// StubKind when callers only need a yes/no.
func IsStub(id string) bool {
return StubKind(id) != ""
}
// StubKind extracts the stub category (stdlib / builtin /
// external_call / module) from id. Returns "" if id is not a
// stub.
//
// Format dispatch:
// - "<kind>::<rest>" — legacy, no repo prefix
// - "<repo>::<kind>::<rest>" — per-repo prefix
//
// We match by looking for one of the known kind segments
// anywhere in the first two "::"-separated positions.
func StubKind(id string) string {
for _, k := range stubKinds {
// Without repo prefix: "<kind>::..."
if strings.HasPrefix(id, k+"::") {
return k
}
}
// With repo prefix: "<repo>::<kind>::..."
// Find the second "::" segment.
first := strings.Index(id, "::")
if first < 0 {
return ""
}
rest := id[first+2:]
for _, k := range stubKinds {
if strings.HasPrefix(rest, k+"::") {
return k
}
}
return ""
}
// stubKinds is the closed set of stub categories. Ordered by
// expected frequency so the lookup loop bails early in the
// common case.
var stubKinds = []string{
StubKindStdlib,
StubKindExternalCall,
StubKindBuiltin,
StubKindModule,
}
// IsStdlibStub etc are convenience predicates that don't make
// the caller compare StubKind's return against a literal.
func IsStdlibStub(id string) bool { return StubKind(id) == StubKindStdlib }
func IsBuiltinStub(id string) bool { return StubKind(id) == StubKindBuiltin }
func IsExternalCallStub(id string) bool { return StubKind(id) == StubKindExternalCall }
func IsModuleStub(id string) bool { return StubKind(id) == StubKindModule }
// StubRest returns the kind-specific tail of a stub id (the
// portion after "<repo>::<kind>::" or "<kind>::"). Returns "" if
// id is not a stub. Useful for the "fmt::Errorf" portion of a
// stdlib stub when callers need to inspect the symbol identity.
func StubRest(id string) string {
kind := StubKind(id)
if kind == "" {
return ""
}
prefix := kind + "::"
if idx := strings.Index(id, prefix); idx >= 0 {
return id[idx+len(prefix):]
}
return ""
}
// UnresolvedMarker is the prefix the extractor emits for a call/
// reference target the resolver still needs to bind to a concrete
// Node.
//
// Forms:
//
// unresolved::Name — legacy / single-repo
// <repoPrefix>::unresolved::Name — multi-repo COPY rewrite (in
// copyBulkLocked, to dodge
// cross-repo PK collisions)
//
// IsUnresolvedTarget / UnresolvedName / UnresolvedRepoPrefix
// normalise over both shapes so callers (resolver, MCP filters,
// data-flow tracker) don't have to know the encoding.
const UnresolvedMarker = "unresolved::"
// FnValuePlaceholderMarker is the sub-namespace the function-as-value capture
// gate (parser/languages/fn_value_capture.go) owns inside the unresolved
// space: a captured function value parks at `unresolved::fnvalue::<name>` until
// resolver.ResolveFnValueCallbacks binds it to a same-file definition. These
// placeholders are scaffolding for that gate alone — the master name resolver
// can never bind them — so the resolver pending scan (EdgesWithUnresolvedTarget)
// excludes the namespace via IsFnValuePlaceholder; the gate finds them itself
// by walking EdgesByKind(references).
const FnValuePlaceholderMarker = UnresolvedMarker + "fnvalue::"
// IsUnresolvedTarget reports whether id names an unresolved
// extractor stub in either the bare or the multi-repo form.
func IsUnresolvedTarget(id string) bool {
if id == "" {
return false
}
if strings.HasPrefix(id, UnresolvedMarker) {
return true
}
return strings.Contains(id, "::"+UnresolvedMarker)
}
// IsFnValuePlaceholder reports whether id is a fn-value gate placeholder in
// either the bare `unresolved::fnvalue::<name>` form or the multi-repo
// `<repoPrefix>::unresolved::fnvalue::<name>` COPY-rewrite form — mirroring
// IsUnresolvedTarget's two shapes. The fn-value gate owns this namespace; the
// resolver pending scan excludes it.
func IsFnValuePlaceholder(id string) bool {
if id == "" {
return false
}
if strings.HasPrefix(id, FnValuePlaceholderMarker) {
return true
}
return strings.Contains(id, "::"+FnValuePlaceholderMarker)
}
// UnresolvedName returns the bare symbol name encoded in an
// unresolved target id, stripping the `unresolved::` prefix (and
// any leading `<repoPrefix>::`). Returns "" when id is not an
// unresolved stub.
func UnresolvedName(id string) string {
if id == "" {
return ""
}
if strings.HasPrefix(id, UnresolvedMarker) {
return id[len(UnresolvedMarker):]
}
idx := strings.Index(id, "::"+UnresolvedMarker)
if idx < 0 {
return ""
}
return id[idx+len("::"+UnresolvedMarker):]
}
// UnresolvedRepoPrefix returns the per-repo prefix encoded in an
// unresolved target id, or "" if the id is bare or not an
// unresolved stub.
func UnresolvedRepoPrefix(id string) string {
if id == "" || strings.HasPrefix(id, UnresolvedMarker) {
return ""
}
idx := strings.Index(id, "::"+UnresolvedMarker)
if idx <= 0 {
return ""
}
return id[:idx]
}
// StubRepoPrefix returns the per-repo prefix of a stub id, or
// "" if the id has no prefix or isn't a stub.
func StubRepoPrefix(id string) string {
kind := StubKind(id)
if kind == "" {
return ""
}
// If id starts with the kind directly, there's no repo prefix.
if strings.HasPrefix(id, kind+"::") {
return ""
}
if idx := strings.Index(id, "::"); idx > 0 {
return id[:idx]
}
return ""
}
// IsResolvableRefEdge reports whether an edge of this kind is a
// symbol-level reference that the resolver binds from an
// `unresolved::<Name>` stub — calls, references, value reads/writes,
// type positions (typed_as / returns), and type hierarchy
// (implements / extends / composes / instantiates). These are the edges
// that must survive a definition's re-index as pending stubs rather than
// be dropped wholesale. Structural edges (contains / defines / member_of
// / imports / param_of) and enrichment edges (tests / provides / spawns
// / annotated / …) are not name-resolved and are excluded — re-stubbing
// them would only create edges nothing ever rebinds.
func IsResolvableRefEdge(k EdgeKind) bool {
switch k {
case EdgeCalls, EdgeReferences, EdgeReads, EdgeWrites,
EdgeTypedAs, EdgeReturns, EdgeInstantiates,
EdgeImplements, EdgeExtends, EdgeComposes:
return true
}
return false
}
// IsReferenceableSymbol reports whether a node of this kind can be the
// target of a cross-file symbol reference — and thus the subject of
// reverse resolution by name. Excludes files, imports, packages,
// params, closures, locals, builtins, generic params, and the
// coverage / infra node kinds, none of which a caller binds to by bare
// name from an unresolved stub.
func IsReferenceableSymbol(k NodeKind) bool {
switch k {
case KindFunction, KindMethod, KindType, KindInterface,
KindVariable, KindConstant, KindField, KindEnumMember:
return true
}
return false
}