godoc: show earliest version when identifier was added

CL 85396 implemented parsePackageAPIInfo with the idea that each
identifier shows up in exactly one of api/go*.txt files, when it
was added. We now know that it may show up more than once, when
the signature changes (generally in a compatible way, such as
when existing types are replaced with aliases to an equivalent
type).

Modify the algorithm to parse the api/go*.txt files in reverse
order, in order to find and display the earliest Go version when
each identifier was first added.

Fixes golang/go#44081.
Updates golang/go#5778.

Change-Id: I83171fd8c161d703f284011375d74b824c279d41
Reviewed-on: https://go-review.googlesource.com/c/tools/+/289089
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Dmitri Shuralyov 2021-02-02 23:38:24 -05:00
parent 5ab06b02d6
commit 6140657873
2 changed files with 53 additions and 5 deletions

View File

@ -13,6 +13,8 @@ import (
"log"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"unicode"
)
@ -75,18 +77,22 @@ type versionParser struct {
res apiVersions // initialized lazily
}
// parseFile parses the named $GOROOT/api/goVERSION.txt file.
//
// For each row, it updates the corresponding entry in
// vp.res to VERSION, overwriting any previous value.
// As a special case, if goVERSION is "go1", it deletes
// from the map instead.
func (vp *versionParser) parseFile(name string) error {
base := filepath.Base(name)
ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go")
if ver == "1" {
return nil
}
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
base := filepath.Base(name)
ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go")
sc := bufio.NewScanner(f)
for sc.Scan() {
row, ok := parseRow(sc.Text())
@ -108,15 +114,31 @@ func (vp *versionParser) parseFile(name string) error {
}
switch row.kind {
case "func":
if ver == "1" {
delete(pkgi.funcSince, row.name)
break
}
pkgi.funcSince[row.name] = ver
case "type":
if ver == "1" {
delete(pkgi.typeSince, row.name)
break
}
pkgi.typeSince[row.name] = ver
case "method":
if ver == "1" {
delete(pkgi.methodSince[row.recv], row.name)
break
}
if _, ok := pkgi.methodSince[row.recv]; !ok {
pkgi.methodSince[row.recv] = make(map[string]string)
}
pkgi.methodSince[row.recv][row.name] = ver
case "field":
if ver == "1" {
delete(pkgi.fieldSince[row.structName], row.name)
break
}
if _, ok := pkgi.fieldSince[row.structName]; !ok {
pkgi.fieldSince[row.structName] = make(map[string]string)
}
@ -214,6 +236,25 @@ func parsePackageAPIInfo() (apiVersions, error) {
return nil, err
}
// Process files in go1.n, go1.n-1, ..., go1.2, go1.1, go1 order.
//
// It's rare, but the signature of an identifier may change
// (for example, a function that accepts a type replaced with
// an alias), and so an existing symbol may show up again in
// a later api/go1.N.txt file. Parsing in reverse version
// order means we end up with the earliest version of Go
// when the symbol was added. See golang.org/issue/44081.
//
ver := func(name string) int {
base := filepath.Base(name)
ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go1.")
if ver == "go1" {
return 0
}
v, _ := strconv.Atoi(ver)
return v
}
sort.Slice(files, func(i, j int) bool { return ver(files[i]) > ver(files[j]) })
vp := new(versionParser)
for _, f := range files {
if err := vp.parseFile(f); err != nil {

View File

@ -113,6 +113,13 @@ func TestAPIVersion(t *testing.T) {
{"type", "strings", "Builder", "", "1.10"},
{"method", "strings", "WriteString", "*Builder", "1.10"},
// Should get the earliest Go version when an identifier
// was initially added, rather than a later version when
// it may have been updated. See issue 44081.
{"func", "os", "Chmod", "", ""}, // Go 1 era function, updated in Go 1.16.
{"method", "os", "Readdir", "*File", ""}, // Go 1 era method, updated in Go 1.16.
{"method", "os", "ReadDir", "*File", "1.16"}, // New to Go 1.16.
// Things from package syscall should never appear
{"func", "syscall", "FchFlags", "", ""},
{"type", "syscall", "Inet4Pktinfo", "", ""},