gopls/internal/golang: provide version info for stdlib fields

For golang/go#67159

Change-Id: I8b8b12949566857f29460675b9dc4d9c6804ff1e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/595336
Auto-Submit: Hongxiang Jiang <hxjiang@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/gopls/doc/release/v0.17.0.md b/gopls/doc/release/v0.17.0.md
index fff5798..65b835d 100644
--- a/gopls/doc/release/v0.17.0.md
+++ b/gopls/doc/release/v0.17.0.md
@@ -12,7 +12,7 @@
 ## Extract declarations to new file
 Gopls now offers another code action, "Extract declarations to new file",
 which moves selected code sections to a newly created file within the
-same package. The created filename is chosen as the first {function, type, 
+same package. The created filename is chosen as the first {function, type,
 const, var} name encountered. In addition, import declarations are added or
 removed as needed.
 
@@ -22,3 +22,9 @@
 
 In order to avoid ambiguity and surprise about what to extract, some kinds
 of paritial selection of a declration cannot invoke this code action.
+
+## Standard library version information in Hover
+
+Hovering over a standard library symbol now displays information about the first
+Go release containing the symbol. For example, hovering over `errors.As` shows
+"Added in go1.13".
diff --git a/gopls/internal/golang/hover.go b/gopls/internal/golang/hover.go
index 089b73e..2edb8a9 100644
--- a/gopls/internal/golang/hover.go
+++ b/gopls/internal/golang/hover.go
@@ -1209,18 +1209,19 @@
 // StdSymbolOf returns the std lib symbol information of the given obj.
 // It returns nil if the input obj is not an exported standard library symbol.
 func StdSymbolOf(obj types.Object) *stdlib.Symbol {
-	if !obj.Exported() {
+	if !obj.Exported() || obj.Pkg() == nil {
+		return nil
+	}
+
+	// Symbols that not defined in standard library should return early.
+	// TODO(hxjiang): The returned slices is binary searchable.
+	symbols := stdlib.PackageSymbols[obj.Pkg().Path()]
+	if symbols == nil {
 		return nil
 	}
 
 	// Handle Function, Type, Const & Var.
 	if isPackageLevel(obj) {
-		// Symbols defined not in std lib package should return early.
-		symbols := stdlib.PackageSymbols[obj.Pkg().Path()]
-		if symbols == nil {
-			return nil
-		}
-		// TODO(hxjiang): This is binary searchable.
 		for _, s := range symbols {
 			if s.Kind == stdlib.Method || s.Kind == stdlib.Field {
 				continue
@@ -1236,7 +1237,7 @@
 	if fn, _ := obj.(*types.Func); fn != nil {
 		isPtr, named := typesinternal.ReceiverNamed(fn.Type().(*types.Signature).Recv())
 		if isPackageLevel(named.Obj()) {
-			for _, s := range stdlib.PackageSymbols[obj.Pkg().Path()] {
+			for _, s := range symbols {
 				if s.Kind != stdlib.Method {
 					continue
 				}
@@ -1249,7 +1250,30 @@
 		}
 	}
 
-	// TODO(hxjiang): handle exported fields of package level types.
+	// Handle Field.
+	if v, _ := obj.(*types.Var); v != nil && v.IsField() {
+		for _, s := range symbols {
+			if s.Kind != stdlib.Field {
+				continue
+			}
+
+			typeName, fieldName := s.SplitField()
+			if fieldName != v.Name() {
+				continue
+			}
+
+			typeObj := obj.Pkg().Scope().Lookup(typeName)
+			if typeObj == nil {
+				continue
+			}
+
+			if fieldObj, _, _ := types.LookupFieldOrMethod(typeObj.Type(), true, obj.Pkg(), fieldName); obj == fieldObj {
+				return &s
+			}
+		}
+		return nil
+	}
+
 	return nil
 }
 
diff --git a/gopls/internal/test/integration/misc/hover_test.go b/gopls/internal/test/integration/misc/hover_test.go
index e2dd037..9c679f0 100644
--- a/gopls/internal/test/integration/misc/hover_test.go
+++ b/gopls/internal/test/integration/misc/hover_test.go
@@ -670,6 +670,7 @@
 import "context"
 import "crypto"
 import "regexp"
+import "go/doc/comment"
 
 type testRegexp = *regexp.Regexp
 
@@ -686,6 +687,9 @@
 	copy := re.Copy()
 	var testRE testRegexp
 	testRE.Longest()
+
+	var pr comment.Printer
+	pr.HeadingID = func(*comment.Heading) string { return "" }
 }
 `
 
@@ -702,7 +706,7 @@
 		{"SHA512_224", true, "go1.5"}, // package-level const
 		{"Copy", true, "go1.6"},       // method
 		{"Longest", true, "go1.1"},    // method with alias receiver
-		// TODO(hxjiang): add test for symbol type Field.
+		{"HeadingID", true, "go1.19"}, // field
 	}
 
 	Run(t, src, func(t *testing.T, env *Env) {