diff --git a/src/cmd/compile/internal/importer/iimport.go b/src/cmd/compile/internal/importer/iimport.go index a4637ec34f9..37e5113435c 100644 --- a/src/cmd/compile/internal/importer/iimport.go +++ b/src/cmd/compile/internal/importer/iimport.go @@ -1,4 +1,3 @@ -// UNREVIEWED // Copyright 2018 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. @@ -42,6 +41,16 @@ func (r *intReader) uint64() uint64 { return i } +// Keep this in sync with constants in iexport.go. +const ( + iexportVersionGo1_11 = 0 + iexportVersionPosCol = 1 + iexportVersionGenerics = 2 + + // Start of the unstable series of versions, remove "+ n" before release. + iexportVersionCurrent = iexportVersionGenerics + 1 +) + const predeclReserved = 32 type itag uint64 @@ -68,7 +77,7 @@ const io_SeekCurrent = 1 // io.SeekCurrent (not defined in Go 1.4) // If the export data version is not recognized or the format is otherwise // compromised, an error is returned. func iImportData(imports map[string]*types2.Package, data []byte, path string) (_ int, pkg *types2.Package, err error) { - const currentVersion = 1 + const currentVersion = iexportVersionCurrent version := int64(-1) defer func() { if e := recover(); e != nil { @@ -84,9 +93,13 @@ func iImportData(imports map[string]*types2.Package, data []byte, path string) ( version = int64(r.uint64()) switch version { - case currentVersion, 0: + case currentVersion, iexportVersionPosCol, iexportVersionGo1_11: default: - errorf("unknown iexport format version %d", version) + if version > iexportVersionGenerics { + errorf("unstable iexport format version %d, just rebuild compiler and std library", version) + } else { + errorf("unknown iexport format version %d", version) + } } sLen := int64(r.uint64()) @@ -98,8 +111,9 @@ func iImportData(imports map[string]*types2.Package, data []byte, path string) ( r.Seek(sLen+dLen, io_SeekCurrent) p := iimporter{ - ipath: path, - version: int(version), + exportVersion: version, + ipath: path, + version: int(version), stringData: stringData, stringCache: make(map[uint64]string), @@ -178,8 +192,9 @@ func iImportData(imports map[string]*types2.Package, data []byte, path string) ( } type iimporter struct { - ipath string - version int + exportVersion int64 + ipath string + version int stringData []byte stringCache map[uint64]string @@ -294,14 +309,20 @@ func (r *importReader) obj(name string) { r.declare(types2.NewConst(pos, r.currPkg, name, typ, val)) case 'F': - tparams := r.tparamList() + var tparams []*types2.TypeName + if r.p.exportVersion >= iexportVersionGenerics { + tparams = r.tparamList() + } sig := r.signature(nil) sig.SetTParams(tparams) r.declare(types2.NewFunc(pos, r.currPkg, name, sig)) case 'T': - tparams := r.tparamList() + var tparams []*types2.TypeName + if r.p.exportVersion >= iexportVersionGenerics { + tparams = r.tparamList() + } // Types can be recursive. We need to setup a stub // declaration before recursing. @@ -592,6 +613,9 @@ func (r *importReader) doType(base *types2.Named) types2.Type { return typ case typeParamType: + if r.p.exportVersion < iexportVersionGenerics { + errorf("unexpected type param type") + } r.currPkg = r.pkg() pos := r.pos() name := r.string() @@ -622,6 +646,9 @@ func (r *importReader) doType(base *types2.Named) types2.Type { return t case instType: + if r.p.exportVersion < iexportVersionGenerics { + errorf("unexpected instantiation type") + } pos := r.pos() len := r.uint64() targs := make([]types2.Type, len) diff --git a/src/cmd/compile/internal/typecheck/iexport.go b/src/cmd/compile/internal/typecheck/iexport.go index 11b9755148e..e6813adbf90 100644 --- a/src/cmd/compile/internal/typecheck/iexport.go +++ b/src/cmd/compile/internal/typecheck/iexport.go @@ -223,9 +223,17 @@ import ( ) // Current indexed export format version. Increase with each format change. -// 1: added column details to Pos // 0: Go1.11 encoding -const iexportVersion = 1 +// 1: added column details to Pos +// 2: added information for generic function/types (currently unstable) +const ( + iexportVersionGo1_11 = 0 + iexportVersionPosCol = 1 + iexportVersionGenerics = 2 + + // Start of the unstable series of versions, remove "+ n" before release. + iexportVersionCurrent = iexportVersionGenerics + 1 +) // predeclReserved is the number of type offsets reserved for types // implicitly declared in the universe block. @@ -297,7 +305,7 @@ func WriteExports(out *bufio.Writer) { // Assemble header. var hdr intWriter hdr.WriteByte('i') - hdr.uint64(iexportVersion) + hdr.uint64(iexportVersionCurrent) hdr.uint64(uint64(p.strings.Len())) hdr.uint64(dataLen) diff --git a/src/cmd/compile/internal/typecheck/iimport.go b/src/cmd/compile/internal/typecheck/iimport.go index b6f227bb00e..778ce4be12a 100644 --- a/src/cmd/compile/internal/typecheck/iimport.go +++ b/src/cmd/compile/internal/typecheck/iimport.go @@ -121,8 +121,14 @@ func ReadImports(pkg *types.Pkg, in *bio.Reader) (fingerprint goobj.FingerprintT ird := &intReader{in, pkg} version := ird.uint64() - if version != iexportVersion { - base.Errorf("import %q: unknown export format version %d", pkg.Path, version) + switch version { + case iexportVersionCurrent, iexportVersionPosCol, iexportVersionGo1_11: + default: + if version > iexportVersionGenerics { + base.Errorf("import %q: unstable export format version %d, just recompile", pkg.Path, version) + } else { + base.Errorf("import %q: unknown export format version %d", pkg.Path, version) + } base.ErrorExit() } @@ -143,7 +149,8 @@ func ReadImports(pkg *types.Pkg, in *bio.Reader) (fingerprint goobj.FingerprintT in.MustSeek(int64(sLen+dLen), os.SEEK_CUR) p := &iimporter{ - ipkg: pkg, + exportVersion: version, + ipkg: pkg, pkgCache: map[uint64]*types.Pkg{}, posBaseCache: map[uint64]*src.PosBase{}, @@ -212,7 +219,8 @@ func ReadImports(pkg *types.Pkg, in *bio.Reader) (fingerprint goobj.FingerprintT } type iimporter struct { - ipkg *types.Pkg + exportVersion uint64 + ipkg *types.Pkg pkgCache map[uint64]*types.Pkg posBaseCache map[uint64]*src.PosBase @@ -314,7 +322,10 @@ func (r *importReader) doDecl(sym *types.Sym) *ir.Name { return n case 'F': - tparams := r.tparamList() + var tparams []*types.Field + if r.p.exportVersion >= iexportVersionGenerics { + tparams = r.tparamList() + } typ := r.signature(nil, tparams) n := importfunc(r.p.ipkg, pos, sym, typ) @@ -322,7 +333,10 @@ func (r *importReader) doDecl(sym *types.Sym) *ir.Name { return n case 'T': - rparams := r.typeList() + var rparams []*types.Type + if r.p.exportVersion >= iexportVersionGenerics { + rparams = r.typeList() + } // Types can be recursive. We need to setup a stub // declaration before recursing. @@ -738,6 +752,9 @@ func (r *importReader) typ1() *types.Type { return t case typeParamType: + if r.p.exportVersion < iexportVersionGenerics { + base.Fatalf("unexpected type param type") + } r.setPkg() pos := r.pos() name := r.string() @@ -761,6 +778,9 @@ func (r *importReader) typ1() *types.Type { return t case instType: + if r.p.exportVersion < iexportVersionGenerics { + base.Fatalf("unexpected instantiation type") + } pos := r.pos() len := r.uint64() targs := make([]*types.Type, len) diff --git a/src/go/internal/gcimporter/gcimporter_test.go b/src/go/internal/gcimporter/gcimporter_test.go index c010dc506e7..286b8a63471 100644 --- a/src/go/internal/gcimporter/gcimporter_test.go +++ b/src/go/internal/gcimporter/gcimporter_test.go @@ -139,7 +139,7 @@ func TestVersionHandling(t *testing.T) { // This package only handles gc export data. // Disable test until we put in the new export version. - if true || runtime.Compiler != "gc" { + if runtime.Compiler != "gc" { t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) } diff --git a/src/go/internal/gcimporter/iimport.go b/src/go/internal/gcimporter/iimport.go index 4416f5b2b92..e003dc9767a 100644 --- a/src/go/internal/gcimporter/iimport.go +++ b/src/go/internal/gcimporter/iimport.go @@ -40,6 +40,16 @@ func (r *intReader) uint64() uint64 { return i } +// Keep this in sync with constants in iexport.go. +const ( + iexportVersionGo1_11 = 0 + iexportVersionPosCol = 1 + iexportVersionGenerics = 2 + + // Start of the unstable series of versions, remove "+ n" before release. + iexportVersionCurrent = iexportVersionGenerics + 1 +) + const predeclReserved = 32 type itag uint64 @@ -64,7 +74,7 @@ const ( // If the export data version is not recognized or the format is otherwise // compromised, an error is returned. func iImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { - const currentVersion = 1 + const currentVersion = iexportVersionCurrent version := int64(-1) defer func() { if e := recover(); e != nil { @@ -80,9 +90,13 @@ func iImportData(fset *token.FileSet, imports map[string]*types.Package, data [] version = int64(r.uint64()) switch version { - case currentVersion, 0: + case currentVersion, iexportVersionPosCol, iexportVersionGo1_11: default: - errorf("unknown iexport format version %d", version) + if version > iexportVersionGenerics { + errorf("unstable iexport format version %d, just rebuild compiler and std library", version) + } else { + errorf("unknown iexport format version %d", version) + } } sLen := int64(r.uint64()) @@ -94,8 +108,9 @@ func iImportData(fset *token.FileSet, imports map[string]*types.Package, data [] r.Seek(sLen+dLen, io.SeekCurrent) p := iimporter{ - ipath: path, - version: int(version), + exportVersion: version, + ipath: path, + version: int(version), stringData: stringData, stringCache: make(map[uint64]string), @@ -173,8 +188,9 @@ func iImportData(fset *token.FileSet, imports map[string]*types.Package, data [] } type iimporter struct { - ipath string - version int + exportVersion int64 + ipath string + version int stringData []byte stringCache map[uint64]string @@ -273,19 +289,22 @@ func (r *importReader) obj(name string) { r.declare(types.NewConst(pos, r.currPkg, name, typ, val)) case 'F': - numTparams := r.uint64() - if numTparams > 0 { - errorf("unexpected tparam") - return + if r.p.exportVersion >= iexportVersionGenerics { + numTparams := r.uint64() + if numTparams > 0 { + errorf("unexpected tparam") + } } sig := r.signature(nil) r.declare(types.NewFunc(pos, r.currPkg, name, sig)) case 'T': - numTparams := r.uint64() - if numTparams > 0 { - errorf("unexpected tparam") + if r.p.exportVersion >= iexportVersionGenerics { + numTparams := r.uint64() + if numTparams > 0 { + errorf("unexpected tparam") + } } // Types can be recursive. We need to setup a stub @@ -562,7 +581,7 @@ func (r *importReader) doType(base *types.Named) types.Type { return typ case typeParamType: - errorf("do not handle tparams yet") + errorf("do not handle type param types yet") return nil case instType: diff --git a/test/typeparam/listimp.dir/a.go b/test/typeparam/listimp.dir/a.go new file mode 100644 index 00000000000..ea569751a67 --- /dev/null +++ b/test/typeparam/listimp.dir/a.go @@ -0,0 +1,50 @@ +package a + +type Ordered interface { + type int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, uintptr, + float32, float64, + string +} + +// List is a linked list of ordered values of type T. +type List[T Ordered] struct { + Next *List[T] + Val T +} + +func (l *List[T]) Largest() T { + var max T + for p := l; p != nil; p = p.Next { + if p.Val > max { + max = p.Val + } + } + return max +} + +type OrderedNum interface { + type int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64, uintptr, + float32, float64 +} + +// ListNum is a linked _List of ordered numeric values of type T. +type ListNum[T OrderedNum] struct { + Next *ListNum[T] + Val T +} + +const Clip = 5 + +// clippedLargest returns the largest in the list of OrderNums, but a max of 5. +// TODO(danscales): fix export/import of an untype constant with typeparam type +func (l *ListNum[T]) ClippedLargest() T { + var max T + for p := l; p != nil; p = p.Next { + if p.Val > max && p.Val < T(Clip) { + max = p.Val + } + } + return max +} diff --git a/test/typeparam/listimp.dir/main.go b/test/typeparam/listimp.dir/main.go new file mode 100644 index 00000000000..4c1aa3e4934 --- /dev/null +++ b/test/typeparam/listimp.dir/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "a" + "fmt" +) + +func main() { + i3 := &a.List[int]{nil, 1} + i2 := &a.List[int]{i3, 3} + i1 := &a.List[int]{i2, 2} + if got, want := i1.Largest(), 3; got != want { + panic(fmt.Sprintf("got %d, want %d", got, want)) + } + + b3 := &a.List[byte]{nil, byte(1)} + b2 := &a.List[byte]{b3, byte(3)} + b1 := &a.List[byte]{b2, byte(2)} + if got, want := b1.Largest(), byte(3); got != want { + panic(fmt.Sprintf("got %d, want %d", got, want)) + } + + f3 := &a.List[float64]{nil, 13.5} + f2 := &a.List[float64]{f3, 1.2} + f1 := &a.List[float64]{f2, 4.5} + if got, want := f1.Largest(), 13.5; got != want { + panic(fmt.Sprintf("got %f, want %f", got, want)) + } + + s3 := &a.List[string]{nil, "dd"} + s2 := &a.List[string]{s3, "aa"} + s1 := &a.List[string]{s2, "bb"} + if got, want := s1.Largest(), "dd"; got != want { + panic(fmt.Sprintf("got %s, want %s", got, want)) + } + j3 := &a.ListNum[int]{nil, 1} + j2 := &a.ListNum[int]{j3, 32} + j1 := &a.ListNum[int]{j2, 2} + if got, want := j1.ClippedLargest(), 2; got != want { + panic(fmt.Sprintf("got %d, want %d", got, want)) + } + g3 := &a.ListNum[float64]{nil, 13.5} + g2 := &a.ListNum[float64]{g3, 1.2} + g1 := &a.ListNum[float64]{g2, 4.5} + if got, want := g1.ClippedLargest(), 4.5; got != want { + panic(fmt.Sprintf("got %f, want %f", got, want)) + } +} diff --git a/test/typeparam/listimp.go b/test/typeparam/listimp.go new file mode 100644 index 00000000000..76930e5e4f6 --- /dev/null +++ b/test/typeparam/listimp.go @@ -0,0 +1,7 @@ +// rundir -G=3 + +// 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 ignored