gopls/internal/cache: fix shadowing bug

This CL fixes a classic Go shadowing bug that may cause
getOneTransitiveRefLocked to return nil spuriously in
some cases. This is unlikely be a common or serious
bug. Thanks to pjw for identifying a test case.

(The mistake was pointed out in passing by a coding agent.)

Change-Id: Ica7703aba1272355dab85e5c858b7d308cb16dca
Reviewed-on: https://go-review.googlesource.com/c/tools/+/847709
Reviewed-by: Peter Weinberger <pjw@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/gopls/internal/cache/check.go b/gopls/internal/cache/check.go
index 6c91ef5..83b2cf7 100644
--- a/gopls/internal/cache/check.go
+++ b/gopls/internal/cache/check.go
@@ -1173,7 +1173,7 @@
 		// See the "cycle detected" bug report above.
 		trefs.refs[sym.Name] = nil
 
-		pkgs := b.s.view.pkgIndex.NewSet()
+		pkgs = b.s.view.pkgIndex.NewSet()
 		for _, sym2 := range n.ph.refs[sym.Name] {
 			if b.nodes[sym2.Package] != nil {
 				pkgs.Add(sym2.Package)
diff --git a/gopls/internal/test/integration/misc/references_test.go b/gopls/internal/test/integration/misc/references_test.go
index 1cc6c59..9a79b34 100644
--- a/gopls/internal/test/integration/misc/references_test.go
+++ b/gopls/internal/test/integration/misc/references_test.go
@@ -570,3 +570,60 @@
 	sort.Strings(got)
 	return got
 }
+
+// TestTransitiveRefShadowing is a regression test for a careless
+// shadowing mistake in packageHandleBuilder.getOneTransitiveRefLocked.
+func TestTransitiveRefShadowing(t *testing.T) {
+	const files = `
+-- go.mod --
+module example.com
+
+go 1.18
+-- a/a.go --
+package a
+
+import "example.com/b"
+
+var _ = b.B{X: 1}
+-- b/b.go --
+package b
+
+import "example.com/c"
+
+type B = c.C
+-- c/c.go --
+package c
+
+import "example.com/d"
+
+type C = d.D
+-- d/d.go --
+package d
+
+type D struct{ X int }
+`
+	Run(t, files, func(t *testing.T, env *Env) {
+		env.OpenFile("a/a.go")
+		env.OpenFile("d/d.go")
+		env.AfterChange(NoDiagnostics())
+
+		// Edit a/a.go while b, c, d are already in state validKey.
+		// Without the fix for shadowing in getOneTransitiveRefLocked,
+		// a's package key is computed without d's localKey, and 0 diagnostics
+		// are cached under H(a_edited, b, c).
+		env.RegexpReplace("a/a.go", "X: 1", "X: 2")
+		env.AfterChange(NoDiagnostics())
+
+		// Revert a/a.go and make an incompatible change to d/d.go.
+		env.RegexpReplace("a/a.go", "X: 2", "X: 1")
+		env.RegexpReplace("d/d.go", "X int", "Y int")
+		env.AfterChange(Diagnostics(env.AtRegexp("a/a.go", "X")))
+
+		// Re-apply the edit to a/a.go. Only a/a.go changed in this step,
+		// so b, c, d are again already in state validKey.
+		// Without the fix, a's key is again H(a_edited, b, c) (omitting d),
+		// hitting the stale cached 0-diagnostics entry from the first edit.
+		env.RegexpReplace("a/a.go", "X: 1", "X: 2")
+		env.AfterChange(Diagnostics(env.AtRegexp("a/a.go", "X")))
+	})
+}