Skip to content

Commit b017a6c

Browse files
fix(indexer): suppress conflicting Go package aliases
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1 parent 53f14e8 commit b017a6c

2 files changed

Lines changed: 82 additions & 8 deletions

File tree

internal/service/indexer.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package service
22

33
import (
4+
"bufio"
45
"context"
56
"crypto/sha256"
67
"encoding/gob"
78
"encoding/hex"
8-
"bufio"
99
"errors"
1010
"fmt"
1111
"log/slog"
@@ -85,7 +85,7 @@ type updateSpool struct {
8585

8686
var (
8787
testBuildBatchReleaseHook func([]parsedBuildNodeBatch, int)
88-
resolveBuildEdges = edgeresolve.Resolve
88+
resolveBuildEdges = edgeresolve.Resolve
8989
)
9090

9191
const (
@@ -1294,6 +1294,7 @@ func collectGoImportPackages(absDir string, opts BuildOptions, parserForExt func
12941294
return nil, err
12951295
}
12961296
packages := make(map[string]string)
1297+
ambiguous := make(map[string]struct{})
12971298
err = walkMatchingFiles(context.Background(), absDir, opts, func(path, relPath string) error {
12981299
if strings.ToLower(filepath.Ext(path)) != ".go" {
12991300
return nil
@@ -1312,11 +1313,13 @@ func collectGoImportPackages(absDir string, opts BuildOptions, parserForExt func
13121313
return nil
13131314
}
13141315
dir := filepath.ToSlash(filepath.Dir(relPath))
1316+
importPath := modulePath
13151317
if dir == "." {
1316-
packages[modulePath] = pkgName
1318+
rememberGoImportPackage(packages, ambiguous, importPath, pkgName)
13171319
return nil
13181320
}
1319-
packages[modulePath+"/"+dir] = pkgName
1321+
importPath = modulePath + "/" + dir
1322+
rememberGoImportPackage(packages, ambiguous, importPath, pkgName)
13201323
return nil
13211324
})
13221325
if err != nil {
@@ -1325,6 +1328,19 @@ func collectGoImportPackages(absDir string, opts BuildOptions, parserForExt func
13251328
return packages, nil
13261329
}
13271330

1331+
1332+
func rememberGoImportPackage(packages map[string]string, ambiguous map[string]struct{}, importPath, pkgName string) {
1333+
if _, blocked := ambiguous[importPath]; blocked {
1334+
return
1335+
}
1336+
if existing, ok := packages[importPath]; ok && existing != pkgName {
1337+
delete(packages, importPath)
1338+
ambiguous[importPath] = struct{}{}
1339+
return
1340+
}
1341+
packages[importPath] = pkgName
1342+
}
1343+
13281344
func readGoModulePath(goModPath string) (string, error) {
13291345
file, err := os.Open(goModPath)
13301346
if err != nil {

internal/service/indexer_test.go

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ func countServiceSQLInList(sql string) int {
8282
}
8383

8484
type recordingGraphStore struct {
85-
t *testing.T
86-
ops []string
87-
nextID uint
88-
nodesByFP map[string][]model.Node
85+
t *testing.T
86+
ops []string
87+
nextID uint
88+
nodesByFP map[string][]model.Node
8989
edges []model.Edge
9090
upsertedEdges [][]model.Edge
9191
}
@@ -604,6 +604,64 @@ func TestBuild_UsesRepoLocalPackageClauseForGoImportAssertions(t *testing.T) {
604604
t.Fatalf("expected implements edge from %d to contracts.Service %d, got %+v", impl.ID, iface.ID, edges)
605605
}
606606

607+
func TestBuild_SuppressesRepoLocalPackageClauseCorrectionOnConflict(t *testing.T) {
608+
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{Logger: gormlogger.Discard})
609+
if err != nil {
610+
t.Fatalf("open db: %v", err)
611+
}
612+
st := gormstore.New(db)
613+
if err := st.AutoMigrate(); err != nil {
614+
t.Fatalf("migrate: %v", err)
615+
}
616+
617+
svc := &GraphService{Store: st, DB: db, Walkers: map[string]*treesitter.Walker{".go": treesitter.NewWalker(treesitter.GoSpec)}, Logger: slog.Default()}
618+
619+
tmpDir := t.TempDir()
620+
mustMkdir := func(rel string) {
621+
if err := os.MkdirAll(filepath.Join(tmpDir, rel), 0o755); err != nil {
622+
t.Fatalf("mkdir %s: %v", rel, err)
623+
}
624+
}
625+
mustWrite := func(rel, content string) {
626+
if err := os.WriteFile(filepath.Join(tmpDir, rel), []byte(content), 0o644); err != nil {
627+
t.Fatalf("write %s: %v", rel, err)
628+
}
629+
}
630+
mustWrite("go.mod", "module github.com/example/project\n\ngo 1.25.0\n")
631+
mustMkdir("internal/api")
632+
mustMkdir("mainpkg")
633+
mustWrite("internal/api/a.go", "package contracts\n\ntype Service interface {\n\tRun()\n}\n")
634+
mustWrite("internal/api/b.go", "package other\n\ntype Service interface {\n\tRun()\n}\n")
635+
mustWrite("mainpkg/main.go", "package mainpkg\n\nimport dep \"github.com/example/project/internal/api\"\n\ntype MyType struct{}\n\nfunc (MyType) Run() {}\n\nvar _ dep.Service = MyType{}\n")
636+
637+
ctx := context.Background()
638+
if _, err := svc.Build(ctx, BuildOptions{Dir: tmpDir}); err != nil {
639+
t.Fatalf("Build: %v", err)
640+
}
641+
642+
impl, err := st.GetNode(ctx, "mainpkg.MyType")
643+
if err != nil || impl == nil {
644+
t.Fatalf("GetNode impl: node=%v err=%v", impl, err)
645+
}
646+
iface, err := st.GetNode(ctx, "contracts.Service")
647+
if err != nil || iface == nil {
648+
t.Fatalf("GetNode iface: node=%v err=%v", iface, err)
649+
}
650+
otherIface, err := st.GetNode(ctx, "other.Service")
651+
if err != nil || otherIface == nil {
652+
t.Fatalf("GetNode other iface: node=%v err=%v", otherIface, err)
653+
}
654+
edges, err := st.GetEdgesFrom(ctx, impl.ID)
655+
if err != nil {
656+
t.Fatalf("GetEdgesFrom: %v", err)
657+
}
658+
for _, edge := range edges {
659+
if edge.Kind == model.EdgeKindImplements && (edge.ToNodeID == iface.ID || edge.ToNodeID == otherIface.ID) {
660+
t.Fatalf("expected conflicting package clauses to suppress alias correction, got implements edge %+v", edge)
661+
}
662+
}
663+
}
664+
607665
func TestNewParsedBuildNodeBatch_DropsRawContentAndOnlyBuildsSourceLinesWhenNeeded(t *testing.T) {
608666
typ := reflect.TypeFor[parsedBuildNodeBatch]()
609667
if _, ok := typ.FieldByName("content"); ok {

0 commit comments

Comments
 (0)