go/internal/gcimporter: add support for the Go 1.18 export data version

Add support for accepting the Go 1.18 export data version (2) to the
x/tools gcimporter, while preserving support for generic code at version
1.

For now, the exporter still outputs version 1.

For golang/go#49040

Change-Id: Ic4547e385ced72b88212d150bf16acaf200a010e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/358034
Trust: Robert Findley <rfindley@google.com>
Trust: Dan Scales <danscales@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: Dan Scales <danscales@google.com>
Reviewed-by: Robert Griesemer <gri@golang.org>
diff --git a/go/internal/gcimporter/iexport.go b/go/internal/gcimporter/iexport.go
index c8fa0ac..2411aa3 100644
--- a/go/internal/gcimporter/iexport.go
+++ b/go/internal/gcimporter/iexport.go
@@ -35,15 +35,15 @@
 // The package path of the top-level package will not be recorded,
 // so that calls to IImportData can override with a provided package path.
 func IExportData(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
-	return iexportCommon(out, fset, false, []*types.Package{pkg})
+	return iexportCommon(out, fset, false, iexportVersion, []*types.Package{pkg})
 }
 
 // IExportBundle writes an indexed export bundle for pkgs to out.
 func IExportBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error {
-	return iexportCommon(out, fset, true, pkgs)
+	return iexportCommon(out, fset, true, iexportVersion, pkgs)
 }
 
-func iexportCommon(out io.Writer, fset *token.FileSet, bundle bool, pkgs []*types.Package) (err error) {
+func iexportCommon(out io.Writer, fset *token.FileSet, bundle bool, version int, pkgs []*types.Package) (err error) {
 	if !debug {
 		defer func() {
 			if e := recover(); e != nil {
@@ -59,6 +59,7 @@
 
 	p := iexporter{
 		fset:        fset,
+		version:     version,
 		allPkgs:     map[*types.Package]bool{},
 		stringIndex: map[string]uint64{},
 		declIndex:   map[types.Object]uint64{},
@@ -122,7 +123,7 @@
 	if bundle {
 		hdr.uint64(bundleVersion)
 	}
-	hdr.uint64(iexportVersion)
+	hdr.uint64(uint64(p.version))
 	hdr.uint64(uint64(p.strings.Len()))
 	hdr.uint64(dataLen)
 
@@ -200,8 +201,9 @@
 }
 
 type iexporter struct {
-	fset *token.FileSet
-	out  *bytes.Buffer
+	fset    *token.FileSet
+	out     *bytes.Buffer
+	version int
 
 	localpkg *types.Package
 
@@ -224,7 +226,7 @@
 }
 
 func (p *iexporter) trace(format string, args ...interface{}) {
-	if !debug {
+	if !trace {
 		// Call sites should also be guarded, but having this check here allows
 		// easily enabling/disabling debug trace statements.
 		return
@@ -330,7 +332,15 @@
 		if tparam, ok := t.(*typeparams.TypeParam); ok {
 			w.tag('P')
 			w.pos(obj.Pos())
-			w.typ(tparam.Constraint(), obj.Pkg())
+			constraint := tparam.Constraint()
+			if p.version >= iexportVersionGo1_18 {
+				implicit := false
+				if iface, _ := constraint.(*types.Interface); iface != nil {
+					implicit = typeparams.IsImplicit(iface)
+				}
+				w.bool(implicit)
+			}
+			w.typ(constraint, obj.Pkg())
 			break
 		}
 
@@ -397,7 +407,7 @@
 }
 
 func (w *exportWriter) pos(pos token.Pos) {
-	if iexportVersion >= iexportVersionPosCol {
+	if w.p.version >= iexportVersionPosCol {
 		w.posV1(pos)
 	} else {
 		w.posV0(pos)
@@ -484,7 +494,6 @@
 
 	// Ensure any referenced declarations are written out too.
 	w.p.pushDecl(obj)
-	w.p.trace("writing ident %s for %s", name, obj)
 	w.string(name)
 	w.pkg(obj.Pkg())
 }
diff --git a/go/internal/gcimporter/iexport_common_test.go b/go/internal/gcimporter/iexport_common_test.go
new file mode 100644
index 0000000..abc6aa6
--- /dev/null
+++ b/go/internal/gcimporter/iexport_common_test.go
@@ -0,0 +1,16 @@
+// Copyright 2021 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 gcimporter
+
+// Temporarily expose version-related functionality so that we can test at
+// specific export data versions.
+
+var IExportCommon = iexportCommon
+
+const (
+	IExportVersion         = iexportVersion
+	IExportVersionGenerics = iexportVersionGenerics
+	IExportVersionGo1_18   = iexportVersionGo1_18
+)
diff --git a/go/internal/gcimporter/iexport_go118_test.go b/go/internal/gcimporter/iexport_go118_test.go
index b361717..c2c81a0 100644
--- a/go/internal/gcimporter/iexport_go118_test.go
+++ b/go/internal/gcimporter/iexport_go118_test.go
@@ -50,26 +50,39 @@
 		t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
 	}
 
-	fset := token.NewFileSet()
-	f, err := parser.ParseFile(fset, "g.go", src, 0)
-	if err != nil {
-		t.Fatal(err)
-	}
-	conf := types.Config{
-		Importer: importer.Default(),
-	}
-	pkg, err := conf.Check("", fset, []*ast.File{f}, nil)
-	if err != nil {
-		t.Fatal(err)
+	// Test at both stages of the 1.18 export data format change.
+	tests := []struct {
+		name    string
+		version int
+	}{
+		{"legacy generics", gcimporter.IExportVersionGenerics},
+		{"go1.18", gcimporter.IExportVersionGo1_18},
 	}
 
-	// export
-	var b bytes.Buffer
-	if err := gcimporter.IExportData(&b, fset, pkg); err != nil {
-		t.Fatal(err)
-	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			fset := token.NewFileSet()
+			f, err := parser.ParseFile(fset, "g.go", src, 0)
+			if err != nil {
+				t.Fatal(err)
+			}
+			conf := types.Config{
+				Importer: importer.Default(),
+			}
+			pkg, err := conf.Check("", fset, []*ast.File{f}, nil)
+			if err != nil {
+				t.Fatal(err)
+			}
 
-	testPkgData(t, fset, pkg, b.Bytes())
+			// export
+			data, err := iexport(fset, test.version, pkg)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			testPkgData(t, fset, test.version, pkg, data)
+		})
+	}
 }
 
 func TestImportTypeparamTests(t *testing.T) {
diff --git a/go/internal/gcimporter/iexport_test.go b/go/internal/gcimporter/iexport_test.go
index b326f30..f035217 100644
--- a/go/internal/gcimporter/iexport_test.go
+++ b/go/internal/gcimporter/iexport_test.go
@@ -55,9 +55,9 @@
 	return ioutil.ReadAll(buf)
 }
 
-func iexport(fset *token.FileSet, pkg *types.Package) ([]byte, error) {
+func iexport(fset *token.FileSet, version int, pkg *types.Package) ([]byte, error) {
 	var buf bytes.Buffer
-	if err := gcimporter.IExportData(&buf, fset, pkg); err != nil {
+	if err := gcimporter.IExportCommon(&buf, fset, false, version, []*types.Package{pkg}); err != nil {
 		return nil, err
 	}
 	return buf.Bytes(), nil
@@ -132,11 +132,12 @@
 		return sorted[i].Path() < sorted[j].Path()
 	})
 
+	version := gcimporter.IExportVersion
 	for _, pkg := range sorted {
-		if exportdata, err := iexport(conf.Fset, pkg); err != nil {
+		if exportdata, err := iexport(conf.Fset, version, pkg); err != nil {
 			t.Error(err)
 		} else {
-			testPkgData(t, conf.Fset, pkg, exportdata)
+			testPkgData(t, conf.Fset, version, pkg, exportdata)
 		}
 
 		if pkg.Name() == "main" || pkg.Name() == "haserrors" {
@@ -146,7 +147,7 @@
 		} else if exportdata, err := readExportFile(bp.PkgObj); err != nil {
 			t.Log("warning:", err)
 		} else {
-			testPkgData(t, conf.Fset, pkg, exportdata)
+			testPkgData(t, conf.Fset, version, pkg, exportdata)
 		}
 	}
 
@@ -162,11 +163,11 @@
 	}
 
 	for i, pkg := range sorted {
-		testPkg(t, conf.Fset, pkg, fset2, pkgs2[i])
+		testPkg(t, conf.Fset, version, pkg, fset2, pkgs2[i])
 	}
 }
 
-func testPkgData(t *testing.T, fset *token.FileSet, pkg *types.Package, exportdata []byte) {
+func testPkgData(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, exportdata []byte) {
 	imports := make(map[string]*types.Package)
 	fset2 := token.NewFileSet()
 	_, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path())
@@ -174,11 +175,11 @@
 		t.Errorf("IImportData(%s): %v", pkg.Path(), err)
 	}
 
-	testPkg(t, fset, pkg, fset2, pkg2)
+	testPkg(t, fset, version, pkg, fset2, pkg2)
 }
 
-func testPkg(t *testing.T, fset *token.FileSet, pkg *types.Package, fset2 *token.FileSet, pkg2 *types.Package) {
-	if _, err := iexport(fset2, pkg2); err != nil {
+func testPkg(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, fset2 *token.FileSet, pkg2 *types.Package) {
+	if _, err := iexport(fset2, version, pkg2); err != nil {
 		t.Errorf("reexport %q: %v", pkg.Path(), err)
 	}
 
@@ -226,7 +227,7 @@
 	}
 
 	// export
-	exportdata, err := iexport(fset1, pkg)
+	exportdata, err := iexport(fset1, gcimporter.IExportVersion, pkg)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -269,7 +270,7 @@
 
 	// export
 	// use a nil fileset here to confirm that it doesn't panic
-	exportdata, err := iexport(nil, pkg1)
+	exportdata, err := iexport(nil, gcimporter.IExportVersion, pkg1)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/go/internal/gcimporter/iimport.go b/go/internal/gcimporter/iimport.go
index 806030d..984414b 100644
--- a/go/internal/gcimporter/iimport.go
+++ b/go/internal/gcimporter/iimport.go
@@ -45,13 +45,20 @@
 }
 
 // Keep this in sync with constants in iexport.go.
+//
+// Temporarily, the x/tools importer accepts generic code at both version 1 and
+// 2. However, version 2 contains some breaking changes on top of version 1:
+//   - the 'implicit' bit is added to exported constraints
+//   - a 'kind' byte is added to constant values (not yet done)
+//
+// Once we've completed the bump to version 2 in the standard library, we'll
+// remove support for generics here at version 1.
 const (
 	iexportVersionGo1_11 = 0
 	iexportVersionPosCol = 1
+	iexportVersionGo1_18 = 2
 	// TODO: before release, change this back to 2.
 	iexportVersionGenerics = iexportVersionPosCol
-
-	iexportVersionCurrent = iexportVersionGenerics
 )
 
 type ident struct {
@@ -124,9 +131,9 @@
 
 	version = int64(r.uint64())
 	switch version {
-	case /* iexportVersionGenerics, */ iexportVersionPosCol, iexportVersionGo1_11:
+	case iexportVersionGo1_18, iexportVersionPosCol, iexportVersionGo1_11:
 	default:
-		if version > iexportVersionGenerics {
+		if version > iexportVersionGo1_18 {
 			errorf("unstable iexport format version %d, just rebuild compiler and std library", version)
 		} else {
 			errorf("unknown iexport format version %d", version)
@@ -437,8 +444,19 @@
 		// bound, save the partial type in tparamIndex before reading the bounds.
 		id := ident{r.currPkg.Name(), name}
 		r.p.tparamIndex[id] = t
-
-		typeparams.SetTypeParamConstraint(t, r.typ())
+		var implicit bool
+		if r.p.exportVersion >= iexportVersionGo1_18 {
+			implicit = r.bool()
+		}
+		constraint := r.typ()
+		if implicit {
+			iface, _ := constraint.(*types.Interface)
+			if iface == nil {
+				errorf("non-interface constraint marked implicit")
+			}
+			typeparams.MarkImplicit(iface)
+		}
+		typeparams.SetTypeParamConstraint(t, constraint)
 
 	case 'V':
 		typ := r.typ()
diff --git a/internal/typeparams/typeparams_go117.go b/internal/typeparams/typeparams_go117.go
index 214eab6..61e07b0 100644
--- a/internal/typeparams/typeparams_go117.go
+++ b/internal/typeparams/typeparams_go117.go
@@ -137,6 +137,10 @@
 	return false
 }
 
+// MarkImplicit does nothing, because this Go version does not have implicit
+// interfaces.
+func MarkImplicit(*types.Interface) {}
+
 // ForNamed returns an empty type parameter list, as type parameters are not
 // supported at this Go version.
 func ForNamed(*types.Named) *TypeParamList {
diff --git a/internal/typeparams/typeparams_go118.go b/internal/typeparams/typeparams_go118.go
index 55c0398..e45896f 100644
--- a/internal/typeparams/typeparams_go118.go
+++ b/internal/typeparams/typeparams_go118.go
@@ -130,6 +130,11 @@
 	return iface.IsImplicit()
 }
 
+// MarkImplicit calls iface.MarkImplicit().
+func MarkImplicit(iface *types.Interface) {
+	iface.MarkImplicit()
+}
+
 // ForNamed extracts the (possibly empty) type parameter object list from
 // named.
 func ForNamed(named *types.Named) *TypeParamList {