internal/lsp/cache: avoid panic in mod diags with redundant requires

modfile.File.SetRequire panics on duplicate requires with conflicting
versions. Avoid the panic by returning an error in this case.

Skip directness diagnostics that run into this error, rather than
invalidating all diagnostics.

Fixes golang/go#50425

Change-Id: Ic6379ecc48581e7fd7b470ed295e449833c351dd
Reviewed-on: https://go-review.googlesource.com/c/tools/+/376394
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/internal/lsp/cache/mod_tidy.go b/internal/lsp/cache/mod_tidy.go
index 7c92746..f6c74c5 100644
--- a/internal/lsp/cache/mod_tidy.go
+++ b/internal/lsp/cache/mod_tidy.go
@@ -212,7 +212,11 @@
 		// vice versa.
 		srcDiag, err := directnessDiagnostic(pm.Mapper, req, snapshot.View().Options().ComputeEdits)
 		if err != nil {
-			return nil, err
+			// We're probably in a bad state if we can't compute a
+			// directnessDiagnostic, but try to keep going so as to not suppress
+			// other, valid diagnostics.
+			event.Error(ctx, "computing directness diagnostic", err)
+			continue
 		}
 		diagnostics = append(diagnostics, srcDiag)
 	}
@@ -428,7 +432,14 @@
 	// Change the directness in the matching require statement. To avoid
 	// reordering the require statements, rewrite all of them.
 	var requires []*modfile.Require
+	seenVersions := make(map[string]string)
 	for _, r := range copied.Require {
+		if seen := seenVersions[r.Mod.Path]; seen != "" && seen != r.Mod.Version {
+			// Avoid a panic in SetRequire below, which panics on conflicting
+			// versions.
+			return nil, fmt.Errorf("%q has conflicting versions: %q and %q", r.Mod.Path, seen, r.Mod.Version)
+		}
+		seenVersions[r.Mod.Path] = r.Mod.Version
 		if r.Mod.Path == req.Mod.Path {
 			requires = append(requires, &modfile.Require{
 				Mod:      r.Mod,