diff --git a/go/analysis/passes/ifaceassert/ifaceassert.go b/go/analysis/passes/ifaceassert/ifaceassert.go index fd2285332c..30130f63ea 100644 --- a/go/analysis/passes/ifaceassert/ifaceassert.go +++ b/go/analysis/passes/ifaceassert/ifaceassert.go @@ -51,6 +51,12 @@ func assertableTo(v, t types.Type) *types.Func { if V == nil || T == nil { return nil } + + // Mitigations for interface comparisons and generics. + // TODO(https://github.com/golang/go/issues/50658): Support more precise conclusion. + if isParameterized(V) || isParameterized(T) { + return nil + } if f, wrongType := types.MissingMethod(V, T, false); wrongType { return f } diff --git a/go/analysis/passes/ifaceassert/parameterized.go b/go/analysis/passes/ifaceassert/parameterized.go new file mode 100644 index 0000000000..1285ecf136 --- /dev/null +++ b/go/analysis/passes/ifaceassert/parameterized.go @@ -0,0 +1,112 @@ +// 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 ifaceassert + +import ( + "go/types" + + "golang.org/x/tools/internal/typeparams" +) + +// isParameterized reports whether typ contains any of the type parameters of tparams. +// +// NOTE: Adapted from go/types/infer.go. If that is exported in a future release remove this copy. +func isParameterized(typ types.Type) bool { + w := tpWalker{ + seen: make(map[types.Type]bool), + } + return w.isParameterized(typ) +} + +type tpWalker struct { + seen map[types.Type]bool +} + +func (w *tpWalker) isParameterized(typ types.Type) (res bool) { + // detect cycles + if x, ok := w.seen[typ]; ok { + return x + } + w.seen[typ] = false + defer func() { + w.seen[typ] = res + }() + + switch t := typ.(type) { + case nil, *types.Basic: // TODO(gri) should nil be handled here? + break + + case *types.Array: + return w.isParameterized(t.Elem()) + + case *types.Slice: + return w.isParameterized(t.Elem()) + + case *types.Struct: + for i, n := 0, t.NumFields(); i < n; i++ { + if w.isParameterized(t.Field(i).Type()) { + return true + } + } + + case *types.Pointer: + return w.isParameterized(t.Elem()) + + case *types.Tuple: + n := t.Len() + for i := 0; i < n; i++ { + if w.isParameterized(t.At(i).Type()) { + return true + } + } + + case *types.Signature: + // t.tparams may not be nil if we are looking at a signature + // of a generic function type (or an interface method) that is + // part of the type we're testing. We don't care about these type + // parameters. + // Similarly, the receiver of a method may declare (rather then + // use) type parameters, we don't care about those either. + // Thus, we only need to look at the input and result parameters. + return w.isParameterized(t.Params()) || w.isParameterized(t.Results()) + + case *types.Interface: + for i, n := 0, t.NumMethods(); i < n; i++ { + if w.isParameterized(t.Method(i).Type()) { + return true + } + } + terms, err := typeparams.InterfaceTermSet(t) + if err != nil { + panic(err) + } + for _, term := range terms { + if w.isParameterized(term.Type()) { + return true + } + } + + case *types.Map: + return w.isParameterized(t.Key()) || w.isParameterized(t.Elem()) + + case *types.Chan: + return w.isParameterized(t.Elem()) + + case *types.Named: + list := typeparams.NamedTypeArgs(t) + for i, n := 0, list.Len(); i < n; i++ { + if w.isParameterized(list.At(i)) { + return true + } + } + + case *typeparams.TypeParam: + return true + + default: + panic(t) // unreachable + } + + return false +} diff --git a/go/analysis/passes/ifaceassert/testdata/src/typeparams/typeparams.go b/go/analysis/passes/ifaceassert/testdata/src/typeparams/typeparams.go index dd0c9b2b59..65709c067a 100644 --- a/go/analysis/passes/ifaceassert/testdata/src/typeparams/typeparams.go +++ b/go/analysis/passes/ifaceassert/testdata/src/typeparams/typeparams.go @@ -34,3 +34,69 @@ func GenericInterfaceAssertionTest[T io.Reader]() { default: } } + +// Issue 50658: Check for type parameters in type switches. +type Float interface { + float32 | float64 +} + +type Doer[F Float] interface { + Do() F +} + +func Underlying[F Float](v Doer[F]) string { + switch v.(type) { + case Doer[float32]: + return "float32!" + case Doer[float64]: + return "float64!" + default: + return "" + } +} + +func DoIf[F Float]() { + // This is a synthetic function to create a non-generic to generic assignment. + // This function does not make much sense. + var v Doer[float32] + if t, ok := v.(Doer[F]); ok { + t.Do() + } +} + +func IsASwitch[F Float, U Float](v Doer[F]) bool { + switch v.(type) { + case Doer[U]: + return true + } + return false +} + +func IsA[F Float, U Float](v Doer[F]) bool { + _, is := v.(Doer[U]) + return is +} + +func LayeredTypes[F Float]() { + // This is a synthetic function cover more isParameterized cases. + type T interface { + foo() struct{ _ map[T][2]chan *F } + } + type V interface { + foo() struct{ _ map[T][2]chan *float32 } + } + var t T + var v V + t, _ = v.(T) + _ = t +} + +type X[T any] struct{} + +func (x X[T]) m(T) {} + +func InstancesOfGenericMethods() { + var x interface{ m(string) } + // _ = x.(X[int]) // BAD. Not enabled as it does not type check. + _ = x.(X[string]) // OK +}