Skip to content

Commit

Permalink
internal/typeparams: delete GenericAssignableTo
Browse files Browse the repository at this point in the history
It is unused and, in the one place we actually wanted to use it,
it was the wrong tool for the job; what we need is unification.

Updates golang/go#59224
Updates golang/go#63982

Change-Id: I05b6fe6f56e3f81f434e76398c20496950822bfb
Reviewed-on: https://go-review.googlesource.com/c/tools/+/634597
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
  • Loading branch information
adonovan committed Dec 9, 2024
1 parent 98c17f1 commit ecb0abc
Show file tree
Hide file tree
Showing 2 changed files with 0 additions and 143 deletions.
72 changes: 0 additions & 72 deletions internal/typeparams/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,75 +66,3 @@ func IsTypeParam(t types.Type) bool {
_, ok := types.Unalias(t).(*types.TypeParam)
return ok
}

// GenericAssignableTo is a generalization of types.AssignableTo that
// implements the following rule for uninstantiated generic types:
//
// If V and T are generic named types, then V is considered assignable to T if,
// for every possible instantiation of V[A_1, ..., A_N], the instantiation
// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N].
//
// If T has structural constraints, they must be satisfied by V.
//
// For example, consider the following type declarations:
//
// type Interface[T any] interface {
// Accept(T)
// }
//
// type Container[T any] struct {
// Element T
// }
//
// func (c Container[T]) Accept(t T) { c.Element = t }
//
// In this case, GenericAssignableTo reports that instantiations of Container
// are assignable to the corresponding instantiation of Interface.
func GenericAssignableTo(ctxt *types.Context, V, T types.Type) bool {
V = types.Unalias(V)
T = types.Unalias(T)

// If V and T are not both named, or do not have matching non-empty type
// parameter lists, fall back on types.AssignableTo.

VN, Vnamed := V.(*types.Named)
TN, Tnamed := T.(*types.Named)
if !Vnamed || !Tnamed {
return types.AssignableTo(V, T)
}

vtparams := VN.TypeParams()
ttparams := TN.TypeParams()
if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || VN.TypeArgs().Len() != 0 || TN.TypeArgs().Len() != 0 {
return types.AssignableTo(V, T)
}

// V and T have the same (non-zero) number of type params. Instantiate both
// with the type parameters of V. This must always succeed for V, and will
// succeed for T if and only if the type set of each type parameter of V is a
// subset of the type set of the corresponding type parameter of T, meaning
// that every instantiation of V corresponds to a valid instantiation of T.

// Minor optimization: ensure we share a context across the two
// instantiations below.
if ctxt == nil {
ctxt = types.NewContext()
}

var targs []types.Type
for i := 0; i < vtparams.Len(); i++ {
targs = append(targs, vtparams.At(i))
}

vinst, err := types.Instantiate(ctxt, V, targs, true)
if err != nil {
panic("type parameters should satisfy their own constraints")
}

tinst, err := types.Instantiate(ctxt, T, targs, true)
if err != nil {
return false
}

return types.AssignableTo(vinst, tinst)
}
71 changes: 0 additions & 71 deletions internal/typeparams/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,74 +204,3 @@ func TestFuncOrigin60628(t *testing.T) {
}
}
}

func TestGenericAssignableTo(t *testing.T) {
tests := []struct {
src string
want bool
}{
// The inciting issue: golang/go#50887.
{`
type T[P any] interface {
Accept(P)
}
type V[Q any] struct {
Element Q
}
func (c V[Q]) Accept(q Q) { c.Element = q }
`, true},

// Various permutations on constraints and signatures.
{`type T[P ~int] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true},
{`type T[P int] interface{ A(P) }; type V[Q ~int] int; func (V[Q]) A(Q) {}`, false},
{`type T[P int|string] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true},
{`type T[P any] interface{ A(P) }; type V[Q any] int; func (V[Q]) A(Q, Q) {}`, false},
{`type T[P any] interface{ int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, false},

// Various structural restrictions on T.
{`type T[P any] interface{ ~int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true},
{`type T[P any] interface{ ~int|string; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true},
{`type T[P any] interface{ int; A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, false},

// Various recursive constraints.
{`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ f *Q }] int; func (V[Q]) A(Q) {}`, true},
{`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ g *Q }] int; func (V[Q]) A(Q) {}`, false},
{`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},
{`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~**Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false},
{`type T[P, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},
{`type T[P ~*X, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false},
{`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},

// In this test case, we reverse the type parameters in the signature of V.A
{`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false},
// It would be nice to return true here: V can only be instantiated with
// [int, int], so the identity of the type parameters should not matter.
{`type T[P, X any] interface{ A(P) X }; type V[Q, Y int] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false},
}

for _, test := range tests {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", "package p; "+test.src, 0)
if err != nil {
t.Fatalf("%s:\n%v", test.src, err)
}
var conf types.Config
pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
if err != nil {
t.Fatalf("%s:\n%v", test.src, err)
}

V := pkg.Scope().Lookup("V").Type()
T := pkg.Scope().Lookup("T").Type()

if types.AssignableTo(V, T) {
t.Fatal("AssignableTo")
}

if got := GenericAssignableTo(nil, V, T); got != test.want {
t.Fatalf("%s:\nGenericAssignableTo(%v, %v) = %v, want %v", test.src, V, T, got, test.want)
}
}
}

0 comments on commit ecb0abc

Please sign in to comment.