gopls/internal/analysis/stdversion: set RunDespiteErrors

This change enables RunDespiteErrors, after auditing the code.
This should give more timely feedback while editing.

Also, it moves the vet/gopls common code (DisallowedSymbols)
to typesinternal.TooNewStdSymbols, out of the gopls module,
in anticipation of adding this analyzer to vet.

Updates golang/go#46136

Change-Id: I8d742bf543c9146376d43ae94f7adae3b453e471
Reviewed-on: https://go-review.googlesource.com/c/tools/+/570138
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md
index 1b005ca..242c40e 100644
--- a/gopls/doc/analyzers.md
+++ b/gopls/doc/analyzers.md
@@ -739,6 +739,8 @@
 through a type alias that is guarded by a Go version constraint.
 
 
+[Full documentation](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stdversion)
+
 **Enabled by default.**
 
 ## **stringintconv**
diff --git a/gopls/internal/analysis/stdversion/stdversion.go b/gopls/internal/analysis/stdversion/stdversion.go
index 3e165ff..fa5cdf5 100644
--- a/gopls/internal/analysis/stdversion/stdversion.go
+++ b/gopls/internal/analysis/stdversion/stdversion.go
@@ -15,8 +15,7 @@
 	"golang.org/x/tools/go/analysis"
 	"golang.org/x/tools/go/analysis/passes/inspect"
 	"golang.org/x/tools/go/ast/inspector"
-	"golang.org/x/tools/gopls/internal/util/slices"
-	"golang.org/x/tools/internal/stdlib"
+	"golang.org/x/tools/internal/typesinternal"
 	"golang.org/x/tools/internal/versions"
 )
 
@@ -35,32 +34,34 @@
 `
 
 var Analyzer = &analysis.Analyzer{
-	Name:     "stdversion",
-	Doc:      Doc,
-	Requires: []*analysis.Analyzer{inspect.Analyzer},
-	Run:      run,
+	Name:             "stdversion",
+	Doc:              Doc,
+	Requires:         []*analysis.Analyzer{inspect.Analyzer},
+	URL:              "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stdversion",
+	RunDespiteErrors: true,
+	Run:              run,
 }
 
 func run(pass *analysis.Pass) (any, error) {
 	// Prior to go1.22, versions.FileVersion returns only the
 	// toolchain version, which is of no use to us, so
 	// disable this analyzer on earlier versions.
-	if !slices.Contains(build.Default.ReleaseTags, "go1.22") {
+	if !slicesContains(build.Default.ReleaseTags, "go1.22") {
 		return nil, nil
 	}
 
-	// disallowedSymbolsMemo returns the set of standard library symbols
+	// disallowedSymbols returns the set of standard library symbols
 	// in a given package that are disallowed at the specified Go version.
 	type key struct {
 		pkg     *types.Package
 		version string
 	}
 	memo := make(map[key]map[types.Object]string) // records symbol's minimum Go version
-	disallowedSymbolsMemo := func(pkg *types.Package, version string) map[types.Object]string {
+	disallowedSymbols := func(pkg *types.Package, version string) map[types.Object]string {
 		k := key{pkg, version}
 		disallowed, ok := memo[k]
 		if !ok {
-			disallowed = DisallowedSymbols(pkg, version)
+			disallowed = typesinternal.TooNewStdSymbols(pkg, version)
 			memo[k] = disallowed
 		}
 		return disallowed
@@ -91,7 +92,7 @@
 		case *ast.Ident:
 			if fileVersion != "" {
 				if obj, ok := pass.TypesInfo.Uses[n]; ok && obj.Pkg() != nil {
-					disallowed := disallowedSymbolsMemo(obj.Pkg(), fileVersion)
+					disallowed := disallowedSymbols(obj.Pkg(), fileVersion)
 					if minVersion, ok := disallowed[origin(obj)]; ok {
 						noun := "module"
 						if fileVersion != pkgVersion {
@@ -107,86 +108,7 @@
 	return nil, nil
 }
 
-// DisallowedSymbols computes the set of package-level symbols
-// exported by pkg that are not available at the specified version.
-// The result maps each symbol to its minimum version.
-//
-// (It is exported for use in gopls' completion.)
-func DisallowedSymbols(pkg *types.Package, version string) map[types.Object]string {
-	disallowed := make(map[types.Object]string)
-
-	// Pass 1: package-level symbols.
-	symbols := stdlib.PackageSymbols[pkg.Path()]
-	for _, sym := range symbols {
-		symver := sym.Version.String()
-		if versions.Before(version, symver) {
-			switch sym.Kind {
-			case stdlib.Func, stdlib.Var, stdlib.Const, stdlib.Type:
-				disallowed[pkg.Scope().Lookup(sym.Name)] = symver
-			}
-		}
-	}
-
-	// Pass 2: fields and methods.
-	//
-	// We allow fields and methods if their associated type is
-	// disallowed, as otherwise we would report false positives
-	// for compatibility shims. Consider:
-	//
-	//   //go:build go1.22
-	//   type T struct { F std.Real } // correct new API
-	//
-	//   //go:build !go1.22
-	//   type T struct { F fake } // shim
-	//   type fake struct { ... }
-	//   func (fake) M () {}
-	//
-	// These alternative declarations of T use either the std.Real
-	// type, introduced in go1.22, or a fake type, for the field
-	// F. (The fakery could be arbitrarily deep, involving more
-	// nested fields and methods than are shown here.) Clients
-	// that use the compatibility shim T will compile with any
-	// version of go, whether older or newer than go1.22, but only
-	// the newer version will use the std.Real implementation.
-	//
-	// Now consider a reference to method M in new(T).F.M() in a
-	// module that requires a minimum of go1.21. The analysis may
-	// occur using a version of Go higher than 1.21, selecting the
-	// first version of T, so the method M is Real.M. This would
-	// spuriously cause the analyzer to report a reference to a
-	// too-new symbol even though this expression compiles just
-	// fine (with the fake implementation) using go1.21.
-	for _, sym := range symbols {
-		symVersion := sym.Version.String()
-		if !versions.Before(version, symVersion) {
-			continue // allowed
-		}
-
-		var obj types.Object
-		switch sym.Kind {
-		case stdlib.Field:
-			typename, name := sym.SplitField()
-			t := pkg.Scope().Lookup(typename)
-			if disallowed[t] == "" {
-				obj, _, _ = types.LookupFieldOrMethod(t.Type(), false, pkg, name)
-			}
-
-		case stdlib.Method:
-			ptr, recvname, name := sym.SplitMethod()
-			t := pkg.Scope().Lookup(recvname)
-			if disallowed[t] == "" {
-				obj, _, _ = types.LookupFieldOrMethod(t.Type(), ptr, pkg, name)
-			}
-		}
-		if obj != nil {
-			disallowed[obj] = symVersion
-		}
-	}
-
-	return disallowed
-}
-
-// Reduced from ../../golang/util.go. Good enough for now.
+// Reduced from x/tools/gopls/internal/golang/util.go. Good enough for now.
 // TODO(adonovan): use ast.IsGenerated in go1.21.
 func isGenerated(f *ast.File) bool {
 	for _, group := range f.Comments {
@@ -218,3 +140,13 @@
 	}
 	return obj
 }
+
+// TODO(adonovan): use go1.21 slices.Contains.
+func slicesContains[S ~[]E, E comparable](slice S, x E) bool {
+	for _, elem := range slice {
+		if elem == x {
+			return true
+		}
+	}
+	return false
+}
diff --git a/gopls/internal/analysis/stdversion/testdata/test.txtar b/gopls/internal/analysis/stdversion/testdata/test.txtar
index 65ee7d4..b339dc2 100644
--- a/gopls/internal/analysis/stdversion/testdata/test.txtar
+++ b/gopls/internal/analysis/stdversion/testdata/test.txtar
@@ -79,6 +79,8 @@
 	new(types.Package).GoVersion() // want `types.GoVersion requires go1.21 or later \(module is go1.20\)`
 }
 
+invalid syntax // exercise RunDespiteErrors
+
 -- sub/tagged.go --
 //go:build go1.21
 
@@ -99,3 +101,4 @@
 
 	new(types.Package).GoVersion() // ok: file requires go1.21
 }
+
diff --git a/gopls/internal/golang/completion/completion.go b/gopls/internal/golang/completion/completion.go
index fe66afa..1a2c7c1 100644
--- a/gopls/internal/golang/completion/completion.go
+++ b/gopls/internal/golang/completion/completion.go
@@ -29,7 +29,6 @@
 
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/tools/go/ast/astutil"
-	"golang.org/x/tools/gopls/internal/analysis/stdversion"
 	"golang.org/x/tools/gopls/internal/cache"
 	"golang.org/x/tools/gopls/internal/cache/metadata"
 	"golang.org/x/tools/gopls/internal/file"
@@ -247,7 +246,7 @@
 	methodSetCache map[methodSetKey]*types.MethodSet
 
 	// tooNewSymbolsCache is a cache of
-	// [stdversion.DisallowedSymbols], recording for each std
+	// [typesinternal.TooNewStdSymbols], recording for each std
 	// package which of its exported symbols are too new for
 	// the version of Go in force in the completion file.
 	// (The value is the minimum version in the form "go1.%d".)
@@ -281,7 +280,7 @@
 	}
 	disallowed, ok := c.tooNewSymbolsCache[pkg]
 	if !ok {
-		disallowed = stdversion.DisallowedSymbols(pkg, c.goversion)
+		disallowed = typesinternal.TooNewStdSymbols(pkg, c.goversion)
 		c.tooNewSymbolsCache[pkg] = disallowed
 	}
 	return disallowed[obj] != ""
diff --git a/gopls/internal/settings/api_json.go b/gopls/internal/settings/api_json.go
index c2a12e3..7a7b841 100644
--- a/gopls/internal/settings/api_json.go
+++ b/gopls/internal/settings/api_json.go
@@ -1187,6 +1187,7 @@
 		{
 			Name:    "stdversion",
 			Doc:     "report uses of too-new standard library symbols\n\nThe stdversion analyzer reports references to symbols in the standard\nlibrary that were introduced by a Go release higher than the one in\nforce in the referring file. (Recall that the file's Go version is\ndefined by the 'go' directive its module's go.mod file, or by a\n\"//go:build go1.X\" build tag at the top of the file.)\n\nThe analyzer does not report a diagnostic for a reference to a \"too\nnew\" field or method of a type that is itself \"too new\", as this may\nhave false positives, for example if fields or methods are accessed\nthrough a type alias that is guarded by a Go version constraint.\n",
+			URL:     "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stdversion",
 			Default: true,
 		},
 		{
diff --git a/internal/typesinternal/toonew.go b/internal/typesinternal/toonew.go
new file mode 100644
index 0000000..cc86487
--- /dev/null
+++ b/internal/typesinternal/toonew.go
@@ -0,0 +1,89 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package typesinternal
+
+import (
+	"go/types"
+
+	"golang.org/x/tools/internal/stdlib"
+	"golang.org/x/tools/internal/versions"
+)
+
+// TooNewStdSymbols computes the set of package-level symbols
+// exported by pkg that are not available at the specified version.
+// The result maps each symbol to its minimum version.
+//
+// The pkg is allowed to contain type errors.
+func TooNewStdSymbols(pkg *types.Package, version string) map[types.Object]string {
+	disallowed := make(map[types.Object]string)
+
+	// Pass 1: package-level symbols.
+	symbols := stdlib.PackageSymbols[pkg.Path()]
+	for _, sym := range symbols {
+		symver := sym.Version.String()
+		if versions.Before(version, symver) {
+			switch sym.Kind {
+			case stdlib.Func, stdlib.Var, stdlib.Const, stdlib.Type:
+				disallowed[pkg.Scope().Lookup(sym.Name)] = symver
+			}
+		}
+	}
+
+	// Pass 2: fields and methods.
+	//
+	// We allow fields and methods if their associated type is
+	// disallowed, as otherwise we would report false positives
+	// for compatibility shims. Consider:
+	//
+	//   //go:build go1.22
+	//   type T struct { F std.Real } // correct new API
+	//
+	//   //go:build !go1.22
+	//   type T struct { F fake } // shim
+	//   type fake struct { ... }
+	//   func (fake) M () {}
+	//
+	// These alternative declarations of T use either the std.Real
+	// type, introduced in go1.22, or a fake type, for the field
+	// F. (The fakery could be arbitrarily deep, involving more
+	// nested fields and methods than are shown here.) Clients
+	// that use the compatibility shim T will compile with any
+	// version of go, whether older or newer than go1.22, but only
+	// the newer version will use the std.Real implementation.
+	//
+	// Now consider a reference to method M in new(T).F.M() in a
+	// module that requires a minimum of go1.21. The analysis may
+	// occur using a version of Go higher than 1.21, selecting the
+	// first version of T, so the method M is Real.M. This would
+	// spuriously cause the analyzer to report a reference to a
+	// too-new symbol even though this expression compiles just
+	// fine (with the fake implementation) using go1.21.
+	for _, sym := range symbols {
+		symVersion := sym.Version.String()
+		if !versions.Before(version, symVersion) {
+			continue // allowed
+		}
+
+		var obj types.Object
+		switch sym.Kind {
+		case stdlib.Field:
+			typename, name := sym.SplitField()
+			if t := pkg.Scope().Lookup(typename); t != nil && disallowed[t] == "" {
+				obj, _, _ = types.LookupFieldOrMethod(t.Type(), false, pkg, name)
+			}
+
+		case stdlib.Method:
+			ptr, recvname, name := sym.SplitMethod()
+			if t := pkg.Scope().Lookup(recvname); t != nil && disallowed[t] == "" {
+				obj, _, _ = types.LookupFieldOrMethod(t.Type(), ptr, pkg, name)
+			}
+		}
+		if obj != nil {
+			disallowed[obj] = symVersion
+		}
+	}
+
+	return disallowed
+}