// Copyright 2022 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 satisfy_test import ( "fmt" "go/ast" "go/importer" "go/parser" "go/token" "go/types" "reflect" "sort" "testing" "golang.org/x/tools/internal/versions" "golang.org/x/tools/refactor/satisfy" ) // This test exercises various operations on core types of type parameters. // (It also provides pretty decent coverage of the non-generic operations.) func TestGenericCoreOperations(t *testing.T) { const src = `package foo import "unsafe" type I interface { f() } type impl struct{} func (impl) f() {} // A big pile of single-serving types that implement I. type A struct{impl} type B struct{impl} type C struct{impl} type D struct{impl} type E struct{impl} type F struct{impl} type G struct{impl} type H struct{impl} type J struct{impl} type K struct{impl} type L struct{impl} type M struct{impl} type N struct{impl} type O struct{impl} type P struct{impl} type Q struct{impl} type R struct{impl} type S struct{impl} type T struct{impl} type U struct{impl} type V struct{impl} type W struct{impl} type X struct{impl} type Generic[T any] struct{impl} func (Generic[T]) g(T) {} type GI[T any] interface{ g(T) } func _[Slice interface{ []I }](s Slice) Slice { s[0] = L{} // I <- L return append(s, A{}) // I <- A } func _[Func interface{ func(I) B }](fn Func) { b := fn(C{}) // I <- C var _ I = b // I <- B } func _[Chan interface{ chan D }](ch Chan) { var i I for i = range ch {} // I <- D _ = i } func _[Chan interface{ chan E }](ch Chan) { var _ I = <-ch // I <- E } func _[Chan interface{ chan I }](ch Chan) { ch <- F{} // I <- F } func _[Map interface{ map[G]H }](m Map) { var k, v I for k, v = range m {} // I <- G, I <- H _, _ = k, v } func _[Map interface{ map[I]K }](m Map) { var _ I = m[J{}] // I <- J, I <- K delete(m, R{}) // I <- R _, _ = m[J{}] } func _[Array interface{ [1]I }](a Array) { a[0] = M{} // I <- M } func _[Array interface{ [1]N }](a Array) { var _ I = a[0] // I <- N } func _[Array interface{ [1]O }](a Array) { var v I for _, v = range a {} // I <- O _ = v } func _[ArrayPtr interface{ *[1]P }](a ArrayPtr) { var v I for _, v = range a {} // I <- P _ = v } func _[Slice interface{ []Q }](s Slice) { var v I for _, v = range s {} // I <- Q _ = v } func _[Func interface{ func() (S, bool) }](fn Func) { var i I i, _ = fn() // I <- S _ = i } func _() I { var _ I = T{} // I <- T var _ I = Generic[T]{} // I <- Generic[T] var _ I = Generic[string]{} // I <- Generic[string] return U{} // I <- U } var _ GI[string] = Generic[string]{} // GI[string] <- Generic[string] // universally quantified constraints: // the type parameter may appear on the left, the right, or both sides. func _[T any](g Generic[T]) GI[T] { return g // GI[T] <- Generic[T] } func _[T any]() { type GI2[T any] interface{ g(string) } var _ GI2[T] = Generic[string]{} // GI2[T] <- Generic[string] } type Gen2[T any] struct{} func (f Gen2[T]) g(string) { global = f } // GI[string] <- Gen2[T] var global GI[string] func _() { var x [3]V // golang/go#56227: the finder should visit calls in the unsafe package. _ = unsafe.Slice(&x[0], func() int { var _ I = x[0]; return 3 }()) // I <- V } func _[P ~struct{F I}]() { _ = P{W{}} _ = P{F: X{}} } ` got := constraints(t, src) want := []string{ "p.GI2[T] <- p.Generic[string]", // implicitly "forall T" quantified "p.GI[T] <- p.Generic[T]", // implicitly "forall T" quantified "p.GI[string] <- p.Gen2[T]", // implicitly "forall T" quantified "p.GI[string] <- p.Generic[string]", "p.I <- p.A", "p.I <- p.B", "p.I <- p.C", "p.I <- p.D", "p.I <- p.E", "p.I <- p.F", "p.I <- p.G", "p.I <- p.Generic[p.T]", "p.I <- p.Generic[string]", "p.I <- p.H", "p.I <- p.J", "p.I <- p.K", "p.I <- p.L", "p.I <- p.M", "p.I <- p.N", "p.I <- p.O", "p.I <- p.P", "p.I <- p.Q", "p.I <- p.R", "p.I <- p.S", "p.I <- p.T", "p.I <- p.U", "p.I <- p.V", "p.I <- p.W", "p.I <- p.X", } if !reflect.DeepEqual(got, want) { t.Fatalf("found unexpected constraints: got %s, want %s", got, want) } } func constraints(t *testing.T, src string) []string { // parse fset := token.NewFileSet() f, err := parser.ParseFile(fset, "p.go", src, 0) if err != nil { t.Fatal(err) // parse error } files := []*ast.File{f} // type-check info := &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), Instances: make(map[*ast.Ident]types.Instance), Scopes: make(map[ast.Node]*types.Scope), Selections: make(map[*ast.SelectorExpr]*types.Selection), } versions.InitFileVersions(info) conf := types.Config{ Importer: importer.Default(), } if _, err := conf.Check("p", fset, files, info); err != nil { t.Fatal(err) // type error } // gather constraints var finder satisfy.Finder finder.Find(info, files) var constraints []string for c := range finder.Result { constraints = append(constraints, fmt.Sprintf("%v <- %v", c.LHS, c.RHS)) } sort.Strings(constraints) return constraints }