go/internal/gcimporter: add support for exporting the 1.18 export format

This CL adds support for the Go 1.18 export format to iexport.go. This
support is based on cmd/compile/internal/typecheck/iexport.go, though
with a different types API and adjusted to build at Go versions below
1.18.

Specifically, this CL adds support for both column details in positions,
and generic types and functions. Along the way, some minor adjustments
are made to aid in debugging and testing.

Notably, after this CL the export data version produced by
go/gcexportdata will depend on the Go version used to build.

As part of this change, TestIExportData_stdlib is unskipped for Go 1.18.

Fixes golang/go#48392

Change-Id: I1b218e0acd4864de5c7b89706192273bc7850ffa
Reviewed-on: https://go-review.googlesource.com/c/tools/+/351029
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: Matthew Dempsky <mdempsky@google.com>
diff --git a/go/internal/gcimporter/bexport.go b/go/internal/gcimporter/bexport.go
index a807d0a..072005a 100644
--- a/go/internal/gcimporter/bexport.go
+++ b/go/internal/gcimporter/bexport.go
@@ -92,16 +92,18 @@
 // BExportData returns binary export data for pkg.
 // If no file set is provided, position info will be missing.
 func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
-	defer func() {
-		if e := recover(); e != nil {
-			if ierr, ok := e.(internalError); ok {
-				err = ierr
-				return
+	if !debug {
+		defer func() {
+			if e := recover(); e != nil {
+				if ierr, ok := e.(internalError); ok {
+					err = ierr
+					return
+				}
+				// Not an internal error; panic again.
+				panic(e)
 			}
-			// Not an internal error; panic again.
-			panic(e)
-		}
-	}()
+		}()
+	}
 
 	p := exporter{
 		fset:          fset,
diff --git a/go/internal/gcimporter/bexport_test.go b/go/internal/gcimporter/bexport_test.go
index 702278e..116a009 100644
--- a/go/internal/gcimporter/bexport_test.go
+++ b/go/internal/gcimporter/bexport_test.go
@@ -21,6 +21,7 @@
 	"golang.org/x/tools/go/buildutil"
 	"golang.org/x/tools/go/internal/gcimporter"
 	"golang.org/x/tools/go/loader"
+	"golang.org/x/tools/internal/typeparams"
 )
 
 var isRace = false
@@ -230,9 +231,14 @@
 		}
 	case *types.Named:
 		y := y.(*types.Named)
-		if x.String() != y.String() {
+		xOrig := typeparams.NamedTypeOrigin(x)
+		yOrig := typeparams.NamedTypeOrigin(y)
+		if sanitizeName(xOrig.String()) != sanitizeName(yOrig.String()) {
 			return fmt.Errorf("unequal named types: %s vs %s", x, y)
 		}
+		if err := equalTypeArgs(typeparams.NamedTypeArgs(x), typeparams.NamedTypeArgs(y)); err != nil {
+			return fmt.Errorf("type arguments: %s", err)
+		}
 	case *types.Pointer:
 		y := y.(*types.Pointer)
 		if err := equalType(x.Elem(), y.Elem()); err != nil {
@@ -262,6 +268,12 @@
 			// 	return fmt.Errorf("receiver: %s", err)
 			// }
 		}
+		if err := equalTypeParams(typeparams.ForSignature(x), typeparams.ForSignature(y)); err != nil {
+			return fmt.Errorf("type params: %s", err)
+		}
+		if err := equalTypeParams(typeparams.RecvTypeParams(x), typeparams.RecvTypeParams(y)); err != nil {
+			return fmt.Errorf("recv type params: %s", err)
+		}
 	case *types.Slice:
 		y := y.(*types.Slice)
 		if err := equalType(x.Elem(), y.Elem()); err != nil {
@@ -297,10 +309,63 @@
 				return fmt.Errorf("tuple element %d: %s", i, err)
 			}
 		}
+	case *typeparams.TypeParam:
+		y := y.(*typeparams.TypeParam)
+		if sanitizeName(x.String()) != sanitizeName(y.String()) {
+			return fmt.Errorf("unequal named types: %s vs %s", x, y)
+		}
+		// For now, just compare constraints by type string to short-circuit
+		// cycles.
+		xc := sanitizeName(x.Constraint().String())
+		yc := sanitizeName(y.Constraint().String())
+		if xc != yc {
+			return fmt.Errorf("unequal constraints: %s vs %s", xc, yc)
+		}
+
+	default:
+		panic(fmt.Sprintf("unexpected %T type", x))
 	}
 	return nil
 }
 
+func equalTypeArgs(x, y *typeparams.TypeList) error {
+	if x.Len() != y.Len() {
+		return fmt.Errorf("unequal lengths: %d vs %d", x.Len(), y.Len())
+	}
+	for i := 0; i < x.Len(); i++ {
+		if err := equalType(x.At(i), y.At(i)); err != nil {
+			return fmt.Errorf("type %d: %s", i, err)
+		}
+	}
+	return nil
+}
+
+func equalTypeParams(x, y *typeparams.TypeParamList) error {
+	if x.Len() != y.Len() {
+		return fmt.Errorf("unequal lengths: %d vs %d", x.Len(), y.Len())
+	}
+	for i := 0; i < x.Len(); i++ {
+		if err := equalType(x.At(i), y.At(i)); err != nil {
+			return fmt.Errorf("type parameter %d: %s", i, err)
+		}
+	}
+	return nil
+}
+
+// sanitizeName removes type parameter debugging markers from an object
+// or type string, to normalize it for comparison.
+// TODO(rfindley): remove once this is no longer necessary.
+func sanitizeName(name string) string {
+	var runes []rune
+	for _, r := range name {
+		if '₀' <= r && r < '₀'+10 {
+			continue // trim type parameter subscripts
+		}
+		runes = append(runes, r)
+	}
+	return string(runes)
+}
+
 // TestVeryLongFile tests the position of an import object declared in
 // a very long input file.  Line numbers greater than maxlines are
 // reported as line 1, not garbage or token.NoPos.
diff --git a/go/internal/gcimporter/iexport.go b/go/internal/gcimporter/iexport.go
index d2fc8b6..be8b745 100644
--- a/go/internal/gcimporter/iexport.go
+++ b/go/internal/gcimporter/iexport.go
@@ -19,11 +19,9 @@
 	"math/big"
 	"reflect"
 	"sort"
-)
 
-// Current indexed export format version. Increase with each format change.
-// 0: Go1.11 encoding
-const iexportVersion = 0
+	"golang.org/x/tools/internal/typeparams"
+)
 
 // Current bundled export format version. Increase with each format change.
 // 0: initial implementation
@@ -44,16 +42,18 @@
 }
 
 func iexportCommon(out io.Writer, fset *token.FileSet, bundle bool, pkgs []*types.Package) (err error) {
-	defer func() {
-		if e := recover(); e != nil {
-			if ierr, ok := e.(internalError); ok {
-				err = ierr
-				return
+	if !debug {
+		defer func() {
+			if e := recover(); e != nil {
+				if ierr, ok := e.(internalError); ok {
+					err = ierr
+					return
+				}
+				// Not an internal error; panic again.
+				panic(e)
 			}
-			// Not an internal error; panic again.
-			panic(e)
-		}
-	}()
+		}()
+	}
 
 	p := iexporter{
 		fset:        fset,
@@ -158,7 +158,7 @@
 		pkgs = append(pkgs, pkg)
 
 		sort.Slice(objs, func(i, j int) bool {
-			return objs[i].Name() < objs[j].Name()
+			return indexName(objs[i]) < indexName(objs[j])
 		})
 	}
 
@@ -175,12 +175,26 @@
 		objs := pkgObjs[pkg]
 		w.uint64(uint64(len(objs)))
 		for _, obj := range objs {
-			w.string(obj.Name())
+			w.string(indexName(obj))
 			w.uint64(index[obj])
 		}
 	}
 }
 
+// indexName returns the 'indexed' name of an object. It differs from
+// obj.Name() only for type parameter names, where we include the subscripted
+// type parameter ID.
+//
+// TODO(rfindley): remove this once we no longer need subscripts.
+func indexName(obj types.Object) (res string) {
+	if _, ok := obj.(*types.TypeName); ok {
+		if tparam, ok := obj.Type().(*typeparams.TypeParam); ok {
+			return types.TypeString(tparam, func(*types.Package) string { return "" })
+		}
+	}
+	return obj.Name()
+}
+
 type iexporter struct {
 	fset *token.FileSet
 	out  *bytes.Buffer
@@ -233,10 +247,11 @@
 type exportWriter struct {
 	p *iexporter
 
-	data     intWriter
-	currPkg  *types.Package
-	prevFile string
-	prevLine int64
+	data       intWriter
+	currPkg    *types.Package
+	prevFile   string
+	prevLine   int64
+	prevColumn int64
 }
 
 func (w *exportWriter) exportPath(pkg *types.Package) string {
@@ -261,8 +276,23 @@
 		if sig.Recv() != nil {
 			panic(internalErrorf("unexpected method: %v", sig))
 		}
-		w.tag('F')
+
+		// Function.
+		if typeparams.ForSignature(sig).Len() == 0 {
+			w.tag('F')
+		} else {
+			w.tag('G')
+		}
 		w.pos(obj.Pos())
+		// The tparam list of the function type is the
+		// declaration of the type params. So, write out the type
+		// params right now. Then those type params will be
+		// referenced via their type offset (via typOff) in all
+		// other places in the signature and function that they
+		// are used.
+		if tparams := typeparams.ForSignature(sig); tparams.Len() > 0 {
+			w.tparamList(tparams, obj.Pkg())
+		}
 		w.signature(sig)
 
 	case *types.Const:
@@ -271,30 +301,46 @@
 		w.value(obj.Type(), obj.Val())
 
 	case *types.TypeName:
+		t := obj.Type()
+
+		if tparam, ok := t.(*typeparams.TypeParam); ok {
+			w.tag('P')
+			w.pos(obj.Pos())
+			w.typ(tparam.Constraint(), obj.Pkg())
+			break
+		}
+
 		if obj.IsAlias() {
 			w.tag('A')
 			w.pos(obj.Pos())
-			w.typ(obj.Type(), obj.Pkg())
+			w.typ(t, obj.Pkg())
 			break
 		}
 
 		// Defined type.
-		w.tag('T')
+		named, ok := t.(*types.Named)
+		if !ok {
+			panic(internalErrorf("%s is not a defined type", t))
+		}
+
+		if typeparams.ForNamed(named).Len() == 0 {
+			w.tag('T')
+		} else {
+			w.tag('U')
+		}
 		w.pos(obj.Pos())
 
+		if typeparams.ForNamed(named).Len() > 0 {
+			w.tparamList(typeparams.ForNamed(named), obj.Pkg())
+		}
+
 		underlying := obj.Type().Underlying()
 		w.typ(underlying, obj.Pkg())
 
-		t := obj.Type()
 		if types.IsInterface(t) {
 			break
 		}
 
-		named, ok := t.(*types.Named)
-		if !ok {
-			panic(internalErrorf("%s is not a defined type", t))
-		}
-
 		n := named.NumMethods()
 		w.uint64(uint64(n))
 		for i := 0; i < n; i++ {
@@ -318,6 +364,48 @@
 }
 
 func (w *exportWriter) pos(pos token.Pos) {
+	if iexportVersion >= iexportVersionPosCol {
+		w.posV1(pos)
+	} else {
+		w.posV0(pos)
+	}
+}
+
+func (w *exportWriter) posV1(pos token.Pos) {
+	if w.p.fset == nil {
+		w.int64(0)
+		return
+	}
+
+	p := w.p.fset.Position(pos)
+	file := p.Filename
+	line := int64(p.Line)
+	column := int64(p.Column)
+
+	deltaColumn := (column - w.prevColumn) << 1
+	deltaLine := (line - w.prevLine) << 1
+
+	if file != w.prevFile {
+		deltaLine |= 1
+	}
+	if deltaLine != 0 {
+		deltaColumn |= 1
+	}
+
+	w.int64(deltaColumn)
+	if deltaColumn&1 != 0 {
+		w.int64(deltaLine)
+		if deltaLine&1 != 0 {
+			w.string(file)
+		}
+	}
+
+	w.prevFile = file
+	w.prevLine = line
+	w.prevColumn = column
+}
+
+func (w *exportWriter) posV0(pos token.Pos) {
 	if w.p.fset == nil {
 		w.int64(0)
 		return
@@ -361,8 +449,7 @@
 func (w *exportWriter) qualifiedIdent(obj types.Object) {
 	// Ensure any referenced declarations are written out too.
 	w.p.pushDecl(obj)
-
-	w.string(obj.Name())
+	w.string(indexName(obj))
 	w.pkg(obj.Pkg())
 }
 
@@ -398,9 +485,22 @@
 func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
 	switch t := t.(type) {
 	case *types.Named:
+		if targs := typeparams.NamedTypeArgs(t); targs.Len() > 0 {
+			w.startType(instanceType)
+			// TODO(rfindley): investigate if this position is correct, and if it
+			// matters.
+			w.pos(t.Obj().Pos())
+			w.typeList(targs, pkg)
+			w.typ(typeparams.NamedTypeOrigin(t), pkg)
+			return
+		}
 		w.startType(definedType)
 		w.qualifiedIdent(t.Obj())
 
+	case *typeparams.TypeParam:
+		w.startType(typeParamType)
+		w.qualifiedIdent(t.Obj())
+
 	case *types.Pointer:
 		w.startType(pointerType)
 		w.typ(t.Elem(), pkg)
@@ -461,9 +561,14 @@
 		n := t.NumEmbeddeds()
 		w.uint64(uint64(n))
 		for i := 0; i < n; i++ {
-			f := t.Embedded(i)
-			w.pos(f.Obj().Pos())
-			w.typ(f.Obj().Type(), f.Obj().Pkg())
+			ft := t.EmbeddedType(i)
+			tPkg := pkg
+			if named, _ := ft.(*types.Named); named != nil {
+				w.pos(named.Obj().Pos())
+			} else {
+				w.pos(token.NoPos)
+			}
+			w.typ(ft, tPkg)
 		}
 
 		n = t.NumExplicitMethods()
@@ -476,6 +581,16 @@
 			w.signature(sig)
 		}
 
+	case *typeparams.Union:
+		w.startType(unionType)
+		nt := t.Len()
+		w.uint64(uint64(nt))
+		for i := 0; i < nt; i++ {
+			term := t.Term(i)
+			w.bool(term.Tilde())
+			w.typ(term.Type(), pkg)
+		}
+
 	default:
 		panic(internalErrorf("unexpected type: %v, %v", t, reflect.TypeOf(t)))
 	}
@@ -497,6 +612,21 @@
 	}
 }
 
+func (w *exportWriter) typeList(ts *typeparams.TypeList, pkg *types.Package) {
+	w.uint64(uint64(ts.Len()))
+	for i := 0; i < ts.Len(); i++ {
+		w.typ(ts.At(i), pkg)
+	}
+}
+
+func (w *exportWriter) tparamList(list *typeparams.TypeParamList, pkg *types.Package) {
+	ll := uint64(list.Len())
+	w.uint64(ll)
+	for i := 0; i < list.Len(); i++ {
+		w.typ(list.At(i), pkg)
+	}
+}
+
 func (w *exportWriter) paramList(tup *types.Tuple) {
 	n := tup.Len()
 	w.uint64(uint64(n))
@@ -702,7 +832,7 @@
 		return
 	}
 
-	name := obj.Name()
+	name := indexName(obj)
 	if name == "_" {
 		w.string("_")
 		return
diff --git a/go/internal/gcimporter/iexport_go118_test.go b/go/internal/gcimporter/iexport_go118_test.go
new file mode 100644
index 0000000..1710753
--- /dev/null
+++ b/go/internal/gcimporter/iexport_go118_test.go
@@ -0,0 +1,100 @@
+// 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.
+
+//go:build go1.18
+// +build go1.18
+
+package gcimporter_test
+
+import (
+	"bytes"
+	"go/ast"
+	"go/importer"
+	"go/parser"
+	"go/token"
+	"go/types"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"testing"
+
+	"golang.org/x/tools/go/internal/gcimporter"
+)
+
+func TestGenericExport(t *testing.T) {
+	const src = `
+package generic
+
+type T[A, B any] struct { Left A; Right B }
+
+var X T[int, string] = T[int, string]{1, "hi"}
+
+func ToInt[P interface{ ~int }](p P) int { return int(p) }
+
+var IntID = ToInt[int]
+
+type G[C comparable] int
+`
+	testExportSrc(t, []byte(src))
+}
+
+func testExportSrc(t *testing.T, src []byte) {
+	// This package only handles gc export data.
+	if runtime.Compiler != "gc" {
+		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)
+	}
+
+	// export
+	var b bytes.Buffer
+	if err := gcimporter.IExportData(&b, fset, pkg); err != nil {
+		t.Fatal(err)
+	}
+
+	testPkgData(t, fset, pkg, b.Bytes())
+}
+
+func TestImportTypeparamTests(t *testing.T) {
+	// Check go files in test/typeparam.
+	rootDir := filepath.Join(runtime.GOROOT(), "test", "typeparam")
+	list, err := os.ReadDir(rootDir)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, entry := range list {
+		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
+			// For now, only consider standalone go files.
+			continue
+		}
+
+		t.Run(entry.Name(), func(t *testing.T) {
+			filename := filepath.Join(rootDir, entry.Name())
+			src, err := os.ReadFile(filename)
+			if err != nil {
+				t.Fatal(err)
+			}
+			if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) {
+				// We're bypassing the logic of run.go here, so be conservative about
+				// the files we consider in an attempt to make this test more robust to
+				// changes in test/typeparams.
+				t.Skipf("not detected as a run test")
+			}
+			testExportSrc(t, src)
+		})
+	}
+}
diff --git a/go/internal/gcimporter/iexport_test.go b/go/internal/gcimporter/iexport_test.go
index b75d539..5385011 100644
--- a/go/internal/gcimporter/iexport_test.go
+++ b/go/internal/gcimporter/iexport_test.go
@@ -31,7 +31,6 @@
 	"golang.org/x/tools/go/buildutil"
 	"golang.org/x/tools/go/internal/gcimporter"
 	"golang.org/x/tools/go/loader"
-	"golang.org/x/tools/internal/testenv"
 )
 
 func readExportFile(filename string) ([]byte, error) {
@@ -64,7 +63,6 @@
 }
 
 func TestIExportData_stdlib(t *testing.T) {
-	testenv.SkipAfterGo1Point(t, 17)
 	if runtime.Compiler == "gccgo" {
 		t.Skip("gccgo standard library is inaccessible")
 	}
diff --git a/go/internal/gcimporter/iimport.go b/go/internal/gcimporter/iimport.go
index 6f166d7..1fcc87e 100644
--- a/go/internal/gcimporter/iimport.go
+++ b/go/internal/gcimporter/iimport.go
@@ -98,15 +98,17 @@
 func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data []byte, bundle bool, path string) (pkgs []*types.Package, err error) {
 	const currentVersion = 1
 	version := int64(-1)
-	defer func() {
-		if e := recover(); e != nil {
-			if version > currentVersion {
-				err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e)
-			} else {
-				err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e)
+	if !debug {
+		defer func() {
+			if e := recover(); e != nil {
+				if version > currentVersion {
+					err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e)
+				} else {
+					err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e)
+				}
 			}
-		}
-	}()
+		}()
+	}
 
 	r := &intReader{bytes.NewReader(data), path}
 
@@ -381,10 +383,14 @@
 				// rparams of the method (since those are the
 				// typeparams being used in the method sig/body).
 				targs := typeparams.NamedTypeArgs(baseType(msig.Recv().Type()))
-				if len(targs) > 0 {
-					rparams := make([]*typeparams.TypeParam, len(targs))
+				if targs.Len() > 0 {
+					rparams := make([]*typeparams.TypeParam, targs.Len())
 					for i := range rparams {
-						rparams[i], _ = targs[i].(*typeparams.TypeParam)
+						// TODO(rfindley): this is less tolerant than the standard library
+						// go/internal/gcimporter, which calls under(...) and is tolerant
+						// of nil rparams. Bring them in sync by making the standard
+						// library importer stricter.
+						rparams[i] = targs.At(i).(*typeparams.TypeParam)
 					}
 					typeparams.SetRecvTypeParams(msig, rparams)
 				}
@@ -404,7 +410,7 @@
 		tn := types.NewTypeName(pos, r.currPkg, name0, nil)
 		t := typeparams.NewTypeParam(tn, nil)
 		if sub == 0 {
-			errorf("missing subscript")
+			errorf("name %q missing subscript", name)
 		}
 
 		// TODO(rfindley): can we use a different, stable ID?
@@ -575,7 +581,7 @@
 }
 
 func (r *importReader) pos() token.Pos {
-	if r.p.version >= 1 {
+	if r.p.exportVersion >= iexportVersionPosCol {
 		r.posv1()
 	} else {
 		r.posv0()
diff --git a/go/internal/gcimporter/support_go117.go b/go/internal/gcimporter/support_go117.go
index e6403e1..d892273 100644
--- a/go/internal/gcimporter/support_go117.go
+++ b/go/internal/gcimporter/support_go117.go
@@ -9,6 +9,8 @@
 
 import "go/types"
 
+const iexportVersion = iexportVersionGo1_11
+
 func additionalPredeclared() []types.Type {
 	return nil
 }
diff --git a/go/internal/gcimporter/support_go118.go b/go/internal/gcimporter/support_go118.go
index a5c7485..2c98f0a 100644
--- a/go/internal/gcimporter/support_go118.go
+++ b/go/internal/gcimporter/support_go118.go
@@ -9,6 +9,8 @@
 
 import "go/types"
 
+const iexportVersion = iexportVersionGenerics
+
 // additionalPredeclared returns additional predeclared types in go.1.18.
 func additionalPredeclared() []types.Type {
 	return []types.Type{
diff --git a/internal/typeparams/typeparams_go117.go b/internal/typeparams/typeparams_go117.go
index d015ee1..5b8a35c 100644
--- a/internal/typeparams/typeparams_go117.go
+++ b/internal/typeparams/typeparams_go117.go
@@ -46,6 +46,9 @@
 // this Go version. Its methods panic on use.
 type TypeParam struct{ types.Type }
 
+func (*TypeParam) Constraint() types.Type { unsupported(); return nil }
+func (*TypeParam) Obj() *types.TypeName   { unsupported(); return nil }
+
 // TypeParamList is a placeholder for an empty type parameter list.
 type TypeParamList struct{}
 
@@ -118,14 +121,24 @@
 	}
 }
 
-// NamedTypeArgs extracts the (possibly empty) type argument list from named.
-func NamedTypeArgs(*types.Named) []types.Type {
+// NamedTypeArgs returns nil.
+func NamedTypeArgs(*types.Named) *TypeList {
 	return nil
 }
 
+// NamedTypeOrigin is the identity method at this Go version.
+func NamedTypeOrigin(named *types.Named) types.Type {
+	return named
+}
+
 // Term is a placeholder type, as type parameters are not supported at this Go
 // version. Its methods panic on use.
-type Term struct{ types.Type }
+type Term struct{}
+
+func (*Term) Tilde() bool            { unsupported(); return false }
+func (*Term) Type() types.Type       { unsupported(); return nil }
+func (*Term) String() string         { unsupported(); return "" }
+func (*Term) Underlying() types.Type { unsupported(); return nil }
 
 // NewTerm is unsupported at this Go version, and panics.
 func NewTerm(tilde bool, typ types.Type) *Term {
@@ -137,6 +150,9 @@
 // version. Its methods panic on use.
 type Union struct{ types.Type }
 
+func (*Union) Len() int         { return 0 }
+func (*Union) Term(i int) *Term { unsupported(); return nil }
+
 // NewUnion is unsupported at this Go version, and panics.
 func NewUnion(terms []*Term) *Union {
 	unsupported()
diff --git a/internal/typeparams/typeparams_go118.go b/internal/typeparams/typeparams_go118.go
index 3e808e7..b7547d7 100644
--- a/internal/typeparams/typeparams_go118.go
+++ b/internal/typeparams/typeparams_go118.go
@@ -110,17 +110,14 @@
 	n.SetTypeParams(tparams)
 }
 
-// NamedTypeArgs extracts the (possibly empty) type argument list from named.
-func NamedTypeArgs(named *types.Named) []types.Type {
-	targs := named.TypeArgs()
-	numArgs := targs.Len()
+// NamedTypeArgs returns named.TypeArgs().
+func NamedTypeArgs(named *types.Named) *TypeList {
+	return named.TypeArgs()
+}
 
-	typs := make([]types.Type, numArgs)
-	for i := 0; i < numArgs; i++ {
-		typs[i] = targs.At(i)
-	}
-
-	return typs
+// NamedTypeOrigin returns named.Orig().
+func NamedTypeOrigin(named *types.Named) types.Type {
+	return named.Origin()
 }
 
 // Term is an alias for types.Term.