internal/lsp/cache: show type errors on parse errors in other files

CL 295414 suppressed all type checking errors in the presence of parser
errors, but this was a bit overzealous. go/types attempts to suppress
follow-on errors from bad syntax, so as long as we haven't had to modify
the AST and the current file parses, type checking errors can still
provide a decent signal to the user

Fixes golang/go#44736

Change-Id: Icd89404fbae663b8f934a38908eaaa87c561b64a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/297872
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/gopls/internal/regtest/diagnostics/diagnostics_test.go b/gopls/internal/regtest/diagnostics/diagnostics_test.go
index dc5943a..370a034 100644
--- a/gopls/internal/regtest/diagnostics/diagnostics_test.go
+++ b/gopls/internal/regtest/diagnostics/diagnostics_test.go
@@ -1818,7 +1818,6 @@
 }
 
 func TestIssue44736(t *testing.T) {
-	t.Skip("failing test - see golang.org/issues/44736")
 	const files = `
 	-- go.mod --
 module blah.com
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index 0607a16..b028385 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -308,7 +308,7 @@
 		wg           sync.WaitGroup
 
 		mu             sync.Mutex
-		skipTypeErrors bool
+		haveFixedFiles bool
 	)
 	for i, cgf := range m.compiledGoFiles {
 		wg.Add(1)
@@ -328,10 +328,10 @@
 			pkg.compiledGoFiles[i] = pgf
 			files[i], parseErrors[i], actualErrors[i] = pgf.File, pgf.ParseErr, err
 
-			// If we have fixed parse errors in any of the files,
-			// we should hide type errors, as they may be completely nonsensical.
+			// If we have fixed parse errors in any of the files, we should hide type
+			// errors, as they may be completely nonsensical.
 			mu.Lock()
-			skipTypeErrors = skipTypeErrors || fixed
+			haveFixedFiles = haveFixedFiles || fixed
 			mu.Unlock()
 		}(i, cgf)
 	}
@@ -454,6 +454,19 @@
 		}
 	}
 
+	// Our heuristic for whether to show type checking errors is:
+	//  + If any file was 'fixed', don't show type checking errors as we
+	//    can't guarantee that they reference accurate locations in the source.
+	//  + If there is a parse error _in the current file_, suppress type
+	//    errors in that file.
+	//  + Otherwise, show type errors even in the presence of parse errors in
+	//    other package files. go/types attempts to suppress follow-on errors
+	//    due to bad syntax, so on balance type checking errors still provide
+	//    a decent signal/noise ratio as long as the file in question parses.
+
+	// Track URIs with parse errors so that we can suppress type errors for these
+	// files.
+	unparseable := map[span.URI]bool{}
 	if len(parseErrors) != 0 {
 		pkg.hasListOrParseErrors = true
 		for _, e := range parseErrors {
@@ -462,11 +475,14 @@
 				event.Error(ctx, "unable to compute positions for parse errors", err, tag.Package.Of(pkg.ID()))
 				continue
 			}
-			pkg.diagnostics = append(pkg.diagnostics, diags...)
+			for _, diag := range diags {
+				unparseable[diag.URI] = true
+				pkg.diagnostics = append(pkg.diagnostics, diag)
+			}
 		}
 	}
 
-	if pkg.hasListOrParseErrors || skipTypeErrors {
+	if haveFixedFiles {
 		return pkg, nil
 	}
 
@@ -477,8 +493,15 @@
 			event.Error(ctx, "unable to compute positions for type errors", err, tag.Package.Of(pkg.ID()))
 			continue
 		}
-		pkg.diagnostics = append(pkg.diagnostics, diags...)
 		pkg.typeErrors = append(pkg.typeErrors, e.primary)
+		for _, diag := range diags {
+			// If the file didn't parse cleanly, it is highly likely that type
+			// checking errors will be confusing or redundant. But otherwise, type
+			// checking usually provides a good enough signal to include.
+			if !unparseable[diag.URI] {
+				pkg.diagnostics = append(pkg.diagnostics, diag)
+			}
+		}
 	}
 
 	depsErrors, err := snapshot.depsErrors(ctx, pkg)