| // 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 | |
| package typeparams_test | |
| import ( | |
| "bytes" | |
| "go/ast" | |
| "go/build" | |
| "go/importer" | |
| "go/parser" | |
| "go/token" | |
| "go/types" | |
| "strings" | |
| "testing" | |
| ) | |
| // TestAPIConsistency verifies that exported APIs match at Go 1.17 and Go | |
| // 1.18+. | |
| // | |
| // It relies on the convention that the names of type aliases in the typeparams | |
| // package match the names of the types they are aliasing. | |
| // | |
| // This test could be made more precise. | |
| func TestAPIConsistency(t *testing.T) { | |
| api118 := getAPI(buildPackage(t, true)) | |
| api117 := getAPI(buildPackage(t, false)) | |
| for name, api := range api117 { | |
| if api != api118[name] { | |
| t.Errorf("%q: got %s at 1.17, but %s at 1.18+", name, api, api118[name]) | |
| } | |
| delete(api118, name) | |
| } | |
| for name, api := range api118 { | |
| // Go 1.23 has iterator methods that return Seq. | |
| // These methods can't be supported at 1.17. | |
| if strings.Contains(api, "Seq") && api117[name] == "" { | |
| continue | |
| } | |
| if api != api117[name] { | |
| t.Errorf("%q: got %s at 1.18+, but %s at 1.17", name, api, api117[name]) | |
| } | |
| } | |
| } | |
| func getAPI(pkg *types.Package) map[string]string { | |
| api := make(map[string]string) | |
| for _, name := range pkg.Scope().Names() { | |
| if !token.IsExported(name) { | |
| continue | |
| } | |
| api[name] = name | |
| obj := pkg.Scope().Lookup(name) | |
| if f, ok := obj.(*types.Func); ok { | |
| api[name] = formatSignature(f.Type().(*types.Signature)) | |
| } | |
| typ := pkg.Scope().Lookup(name).Type() | |
| // Consider method sets of pointer and non-pointer receivers. | |
| msets := map[string]*types.MethodSet{ | |
| name: types.NewMethodSet(typ), | |
| "*" + name: types.NewMethodSet(types.NewPointer(typ)), | |
| } | |
| for name, mset := range msets { | |
| for i := 0; i < mset.Len(); i++ { | |
| f := mset.At(i).Obj().(*types.Func) | |
| mname := f.Name() | |
| if token.IsExported(mname) { | |
| api[name+"."+mname] = formatSignature(f.Type().(*types.Signature)) | |
| } | |
| } | |
| } | |
| } | |
| return api | |
| } | |
| func formatSignature(sig *types.Signature) string { | |
| var b bytes.Buffer | |
| b.WriteString("func") | |
| writeTuple(&b, sig.Params()) | |
| writeTuple(&b, sig.Results()) | |
| return b.String() | |
| } | |
| func writeTuple(buf *bytes.Buffer, t *types.Tuple) { | |
| buf.WriteRune('(') | |
| // The API at Go 1.18 uses aliases for types in go/types. These types are | |
| // _actually_ in the go/types package, and therefore would be formatted as | |
| // e.g. *types.TypeParam, which would not match *typeparams.TypeParam -- | |
| // go/types does not track aliases. As we use the same name for all aliases, | |
| // we can make the formatted signatures match by dropping the package | |
| // qualifier. | |
| qf := func(*types.Package) string { return "" } | |
| for i := 0; i < t.Len(); i++ { | |
| if i > 0 { | |
| buf.WriteString(", ") | |
| } | |
| buf.WriteString(types.TypeString(t.At(i).Type(), qf)) | |
| } | |
| buf.WriteRune(')') | |
| } | |
| func buildPackage(t *testing.T, go118 bool) *types.Package { | |
| ctxt := build.Default | |
| if !go118 { | |
| for i, tag := range ctxt.ReleaseTags { | |
| if tag == "go1.18" { | |
| ctxt.ReleaseTags = ctxt.ReleaseTags[:i] | |
| break | |
| } | |
| } | |
| } | |
| bpkg, err := ctxt.ImportDir(".", 0) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| return typeCheck(t, bpkg.GoFiles) | |
| } | |
| func typeCheck(t *testing.T, filenames []string) *types.Package { | |
| fset := token.NewFileSet() | |
| var files []*ast.File | |
| for _, name := range filenames { | |
| f, err := parser.ParseFile(fset, name, nil, 0) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| files = append(files, f) | |
| } | |
| conf := types.Config{ | |
| Importer: importer.Default(), | |
| } | |
| pkg, err := conf.Check("", fset, files, nil) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| return pkg | |
| } |