blob: 98f7ec46cc14feb35a17e26047112906dd957958 [file] [log] [blame]
// 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 typeparams_test
import (
"go/ast"
"go/parser"
"go/token"
"go/types"
"regexp"
"strings"
"testing"
. "golang.org/x/exp/typeparams"
)
func TestNormalTerms(t *testing.T) {
if !Enabled() {
t.Skip("typeparams are not enabled")
}
// In the following tests, src must define a type T with (at least) one type
// parameter. We will compute the normal terms of the first type parameter.
tests := []struct {
src string
want string
wantError string
}{
{"package emptyinterface0; type T[P interface{}] int", "all", ""},
{"package emptyinterface1; type T[P interface{ int | interface{} }] int", "all", ""},
{"package singleton; type T[P interface{ int }] int", "int", ""},
{"package under; type T[P interface{~int}] int", "~int", ""},
{"package superset; type T[P interface{ ~int | int }] int", "~int", ""},
{"package overlap; type T[P interface{ ~int; int }] int", "int", ""},
{"package emptyintersection; type T[P interface{ ~int; string }] int", "", "empty type set"},
{"package embedded0; type T[P interface{ I }] int; type I interface { int }", "int", ""},
{"package embedded1; type T[P interface{ I | string }] int; type I interface{ int | ~string }", "int ?\\| ?~string", ""},
{"package embedded2; type T[P interface{ I; string }] int; type I interface{ int | ~string }", "string", ""},
{"package named; type T[P C] int; type C interface{ ~int|int }", "~int", ""},
{`// package example is taken from the docstring for StructuralTerms
package example
type A interface{ ~string|~[]byte }
type B interface{ int|string }
type C interface { ~string|~int }
type T[P interface{ A|B; C }] int
`, "~string ?\\| ?int", ""},
}
for _, test := range tests {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", test.src, 0)
if err != nil {
t.Fatal(err)
}
t.Run(f.Name.Name, func(t *testing.T) {
conf := types.Config{
Error: func(error) {}, // keep going on errors
}
pkg, err := conf.Check("", fset, []*ast.File{f}, nil)
if err != nil {
t.Logf("types.Config.Check: %v", err)
// keep going on type checker errors: we want to assert on behavior of
// invalid code as well.
}
obj := pkg.Scope().Lookup("T")
if obj == nil {
t.Fatal("type T not found")
}
T := ForNamed(obj.Type().(*types.Named)).At(0)
terms, err := NormalTerms(T)
if test.wantError != "" {
if err == nil {
t.Fatalf("StructuralTerms(%s): nil error, want %q", T, test.wantError)
}
if !strings.Contains(err.Error(), test.wantError) {
t.Errorf("StructuralTerms(%s): err = %q, want %q", T, err, test.wantError)
}
return
}
if err != nil {
t.Fatal(err)
}
var got string
if len(terms) == 0 {
got = "all"
} else {
qf := types.RelativeTo(pkg)
got = types.TypeString(NewUnion(terms), qf)
}
want := regexp.MustCompile(test.want)
if !want.MatchString(got) {
t.Errorf("NormalTerms(%s) = %q, want matching %q", T, got, test.want)
}
})
}
}