Repository: kythe/kythe
Pull request #5398 (2022-10-19 17:52:39)

feat(go_indexer): implement ref/writes edges

schroederc: This is for simple variables.  Member writes will be handled in a future change.

Patch:
diff --git a/kythe/go/indexer/BUILD b/kythe/go/indexer/BUILD
index 4caa6f77f..6e8c1024f 100644
--- a/kythe/go/indexer/BUILD
+++ b/kythe/go/indexer/BUILD
@@ -137,6 +137,11 @@ go_indexer_test(
     srcs = ["testdata/basic/locals.go"],
 )
 
+go_indexer_test(
+    name = "writes_test",
+    srcs = ["testdata/basic/writes.go"],
+)
+
 go_indexer_test(
     name = "imports_test",
     srcs = ["testdata/basic/imports.go"],
diff --git a/kythe/go/indexer/emit.go b/kythe/go/indexer/emit.go
index eeeaf6f36..d37d5e501 100644
--- a/kythe/go/indexer/emit.go
+++ b/kythe/go/indexer/emit.go
@@ -205,6 +205,14 @@ type emitter struct {
 	cmap     ast.CommentMap // current file's CommentMap
 }
 
+type refKind int
+
+const (
+	readRef refKind = iota
+	writeRef
+	readWriteRef
+)
+
 // visitIdent handles referring identifiers. Declaring identifiers are handled
 // as part of their parent syntax.
 func (e *emitter) visitIdent(id *ast.Ident, stack stackFunc) {
@@ -249,9 +257,34 @@ func (e *emitter) visitIdent(id *ast.Ident, stack stackFunc) {
 		})
 		return
 	}
-	ref := e.writeRef(id, target, edges.Ref)
+
+	refKind := readRef
+	switch parent := stack(1).(type) {
+	case *ast.AssignStmt:
+		// Check if identifier is being assigned
+		for _, lhs := range parent.Lhs {
+			if lhs == id {
+				refKind = writeRef
+				break
+			}
+		}
+	case *ast.IncDecStmt:
+		refKind = readWriteRef
+	}
+
+	var refs []*spb.VName
+	if refKind == readRef || refKind == readWriteRef {
+		refs = append(refs, e.writeRef(id, target, edges.Ref))
+	}
+	if refKind == writeRef || refKind == readWriteRef {
+		refs = append(refs, e.writeRef(id, target, edges.RefWrites))
+	}
+
 	if e.opts.emitAnchorScopes() {
-		e.writeEdge(ref, e.callContext(stack).vname, edges.ChildOf)
+		parent := e.callContext(stack).vname
+		for _, ref := range refs {
+			e.writeEdge(ref, parent, edges.ChildOf)
+		}
 	}
 	if call, ok := isCall(id, obj, stack); ok {
 		callAnchor := e.writeRef(call, target, edges.RefCall)
diff --git a/kythe/go/util/schema/edges/edges.go b/kythe/go/util/schema/edges/edges.go
index ef8f76b10..404ac2d89 100644
--- a/kythe/go/util/schema/edges/edges.go
+++ b/kythe/go/util/schema/edges/edges.go
@@ -63,6 +63,7 @@ const (
 	RefImports        = Prefix + "ref/imports"
 	RefInit           = Prefix + "ref/init"
 	RefInitImplicit   = Prefix + "ref/init/implicit"
+	RefWrites         = Prefix + "ref/writes"
 	Tagged            = Prefix + "tagged"
 )
 


Test patch:
diff --git a/kythe/go/indexer/testdata/basic/locals.go b/kythe/go/indexer/testdata/basic/locals.go
index 5b4c85a6c..edd8b7bc2 100644
--- a/kythe/go/indexer/testdata/basic/locals.go
+++ b/kythe/go/indexer/testdata/basic/locals.go
@@ -13,7 +13,7 @@ func foo() {
 	//
 	//- @bravo defines/binding Bravo
 	//- Bravo.node/kind variable
-	//- @alpha ref Alpha1
+	//- @alpha ref/writes Alpha1
 	//- !{@alpha defines/binding Alpha1}
 	alpha, bravo := 1, 2
 
@@ -44,7 +44,7 @@ func foo() {
 	//- @#2alpha ref Alpha3
 	//- @bravo ref Bravo
 	if alpha := alpha + 3; alpha < bravo {
-		//- @bravo ref Bravo
+		//- @bravo ref/writes Bravo
 		//- @alpha ref Alpha3
 		bravo = alpha
 	}
diff --git a/kythe/go/indexer/testdata/basic/scopes.go b/kythe/go/indexer/testdata/basic/scopes.go
index 0c8607720..099f7d070 100644
--- a/kythe/go/indexer/testdata/basic/scopes.go
+++ b/kythe/go/indexer/testdata/basic/scopes.go
@@ -5,7 +5,7 @@ var Ident bool
 
 // - @F defines/binding F
 func F() {
-	//- IdentRef=@Ident ref Ident
+	//- IdentRef=@Ident ref/writes Ident
 	//- IdentRef childof F
 	Ident = true
 }
diff --git a/kythe/go/indexer/testdata/basic/writes.go b/kythe/go/indexer/testdata/basic/writes.go
new file mode 100644
index 000000000..cc87f079b
--- /dev/null
+++ b/kythe/go/indexer/testdata/basic/writes.go
@@ -0,0 +1,28 @@
+// Package writes tests ref/writes edges
+package writes
+
+import "fmt"
+
+func f() {
+	//- @x defines/binding X
+	var x int
+
+	//- @x ref/writes X
+	//- @y defines/binding Y
+	x, y := 1, 2
+
+	//- @y ref Y
+	//- @y ref/writes Y
+	y++
+
+	//- @#0i defines/binding I
+	//- @#1i ref I
+	//- @#2i ref/writes I
+	//- @#3i ref I
+	for i := 0; i < 10; i = i + 1 {
+	}
+
+	//- @x ref X
+	//- @y ref Y
+	fmt.Println(x, y)
+}