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

140 lines
4.0 KiB
Go

package resolver
import (
"path"
"strings"
"github.com/zzet/gortex/internal/graph"
)
// resolveRustModuleImports binds Rust `use crate::…` / `use self::…` /
// `use super::…` import edges to the module file they reference, so a
// module-level dependency shows up in get_dependencies. The Rust extractor
// lowers `use crate::foo::bar;` to an `unresolved::import::crate/foo/bar` edge
// (the `::` separators rewritten to `/`); this pass anchors the path —
// `crate` at the crate src root, `self` at the caller's module directory,
// `super` one directory up per segment — and resolves it longest-prefix-first
// against `foo/bar.rs` | `foo/bar/mod.rs`, dropping trailing segments (the
// imported item or glob) until a module file matches. Refuses on ambiguity
// (both `foo.rs` and `foo/mod.rs` present). Bare paths (external crates or
// unanchored local modules) are left untouched.
//
// Runs inside ResolveRustScopeCalls (the SynthRustScope settle window),
// independent of the call-edge resolution there. Returns the number of import
// edges bound.
func resolveRustModuleImports(g graph.Store) int {
var cands []*graph.Edge
for e := range g.EdgesByKind(graph.EdgeImports) {
if e == nil || !strings.HasPrefix(e.To, "unresolved::import::") {
continue
}
if !strings.HasSuffix(e.From, ".rs") {
continue
}
raw := strings.TrimPrefix(e.To, "unresolved::import::")
i := strings.IndexByte(raw, '/')
if i < 0 {
continue
}
switch raw[:i] {
case "crate", "self", "super":
cands = append(cands, e)
}
}
if len(cands) == 0 {
return 0
}
fileIDs := make(map[string]struct{}, 1024)
for n := range g.NodesByKind(graph.KindFile) {
if n != nil && n.ID != "" {
fileIDs[n.ID] = struct{}{}
}
}
bound := 0
var reindexBatch []graph.EdgeReindex
for _, e := range cands {
raw := strings.TrimPrefix(e.To, "unresolved::import::")
target := resolveRustUseFile(e.From, strings.Split(raw, "/"), fileIDs)
if target == "" || target == e.From {
continue
}
oldTo := e.To
e.To = target
e.Origin = graph.OriginASTResolved
if e.Meta == nil {
e.Meta = map[string]any{}
}
e.Meta["resolved_via"] = "rust_module"
reindexBatch = append(reindexBatch, graph.EdgeReindex{Edge: e, OldTo: oldTo})
bound++
}
if len(reindexBatch) > 0 {
g.ReindexEdges(reindexBatch)
}
return bound
}
// resolveRustUseFile resolves a `/`-joined Rust use path (anchored at
// crate/self/super) to a module file ID, or "" when no unique file matches.
func resolveRustUseFile(srcFile string, segs []string, fileIDs map[string]struct{}) string {
if len(segs) == 0 {
return ""
}
dir := path.Dir(srcFile)
rest := segs
switch segs[0] {
case "crate":
dir = rustCrateRootDir(srcFile)
rest = segs[1:]
case "self":
rest = segs[1:]
case "super":
for len(rest) > 0 && rest[0] == "super" {
dir = path.Dir(dir)
rest = rest[1:]
}
default:
return ""
}
// Longest-prefix-first: try the full module path, then drop the trailing
// segment (the imported item / glob) until a module file matches.
for n := len(rest); n >= 1; n-- {
stem := path.Join(append([]string{dir}, rest[:n]...)...)
var matches []string
for _, cand := range []string{stem + ".rs", stem + "/mod.rs"} {
if _, ok := fileIDs[cand]; ok {
matches = append(matches, cand)
}
}
if len(matches) == 1 {
return matches[0]
}
if len(matches) > 1 {
return "" // ambiguous module file (foo.rs vs foo/mod.rs)
}
}
// `use crate::*` / `use self::*` — the module's own directory file.
if len(rest) == 0 {
for _, cand := range []string{path.Clean(dir) + ".rs", path.Join(dir, "mod.rs")} {
if _, ok := fileIDs[cand]; ok {
return cand
}
}
}
return ""
}
// rustCrateRootDir returns the crate src root for a Rust file — the nearest
// ancestor directory named `src`, or the file's own directory for a flat crate.
func rustCrateRootDir(srcFile string) string {
dir := path.Dir(srcFile)
for d := dir; d != "." && d != "/" && d != ""; d = path.Dir(d) {
if path.Base(d) == "src" {
return d
}
}
return dir
}