Skip to content

Commit 3c751cd

Browse files
committed
passes/ifaceassert: supress typeparams reports
Supresses reporting when either interface is type parameterized. In principle, we should be able to report when we know two interfaces cannot be unified. This is more complicated with type parameters. Waiting on go/types to provide this complex functionality. Updates #50658 Change-Id: Ib767585c785aea12dbb9e337cc339881a63be57e Reviewed-on: https://go-review.googlesource.com/c/tools/+/380014 Run-TryBot: Tim King <taking@google.com> Reviewed-by: Robert Findley <rfindley@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Trust: Tim King <taking@google.com>
1 parent e7c9de2 commit 3c751cd

File tree

3 files changed

+184
-0
lines changed

3 files changed

+184
-0
lines changed

go/analysis/passes/ifaceassert/ifaceassert.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ func assertableTo(v, t types.Type) *types.Func {
5151
if V == nil || T == nil {
5252
return nil
5353
}
54+
55+
// Mitigations for interface comparisons and generics.
56+
// TODO(https://github.com/golang/go/issues/50658): Support more precise conclusion.
57+
if isParameterized(V) || isParameterized(T) {
58+
return nil
59+
}
5460
if f, wrongType := types.MissingMethod(V, T, false); wrongType {
5561
return f
5662
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
package ifaceassert
5+
6+
import (
7+
"go/types"
8+
9+
"golang.org/x/tools/internal/typeparams"
10+
)
11+
12+
// isParameterized reports whether typ contains any of the type parameters of tparams.
13+
//
14+
// NOTE: Adapted from go/types/infer.go. If that is exported in a future release remove this copy.
15+
func isParameterized(typ types.Type) bool {
16+
w := tpWalker{
17+
seen: make(map[types.Type]bool),
18+
}
19+
return w.isParameterized(typ)
20+
}
21+
22+
type tpWalker struct {
23+
seen map[types.Type]bool
24+
}
25+
26+
func (w *tpWalker) isParameterized(typ types.Type) (res bool) {
27+
// detect cycles
28+
if x, ok := w.seen[typ]; ok {
29+
return x
30+
}
31+
w.seen[typ] = false
32+
defer func() {
33+
w.seen[typ] = res
34+
}()
35+
36+
switch t := typ.(type) {
37+
case nil, *types.Basic: // TODO(gri) should nil be handled here?
38+
break
39+
40+
case *types.Array:
41+
return w.isParameterized(t.Elem())
42+
43+
case *types.Slice:
44+
return w.isParameterized(t.Elem())
45+
46+
case *types.Struct:
47+
for i, n := 0, t.NumFields(); i < n; i++ {
48+
if w.isParameterized(t.Field(i).Type()) {
49+
return true
50+
}
51+
}
52+
53+
case *types.Pointer:
54+
return w.isParameterized(t.Elem())
55+
56+
case *types.Tuple:
57+
n := t.Len()
58+
for i := 0; i < n; i++ {
59+
if w.isParameterized(t.At(i).Type()) {
60+
return true
61+
}
62+
}
63+
64+
case *types.Signature:
65+
// t.tparams may not be nil if we are looking at a signature
66+
// of a generic function type (or an interface method) that is
67+
// part of the type we're testing. We don't care about these type
68+
// parameters.
69+
// Similarly, the receiver of a method may declare (rather then
70+
// use) type parameters, we don't care about those either.
71+
// Thus, we only need to look at the input and result parameters.
72+
return w.isParameterized(t.Params()) || w.isParameterized(t.Results())
73+
74+
case *types.Interface:
75+
for i, n := 0, t.NumMethods(); i < n; i++ {
76+
if w.isParameterized(t.Method(i).Type()) {
77+
return true
78+
}
79+
}
80+
terms, err := typeparams.InterfaceTermSet(t)
81+
if err != nil {
82+
panic(err)
83+
}
84+
for _, term := range terms {
85+
if w.isParameterized(term.Type()) {
86+
return true
87+
}
88+
}
89+
90+
case *types.Map:
91+
return w.isParameterized(t.Key()) || w.isParameterized(t.Elem())
92+
93+
case *types.Chan:
94+
return w.isParameterized(t.Elem())
95+
96+
case *types.Named:
97+
list := typeparams.NamedTypeArgs(t)
98+
for i, n := 0, list.Len(); i < n; i++ {
99+
if w.isParameterized(list.At(i)) {
100+
return true
101+
}
102+
}
103+
104+
case *typeparams.TypeParam:
105+
return true
106+
107+
default:
108+
panic(t) // unreachable
109+
}
110+
111+
return false
112+
}

go/analysis/passes/ifaceassert/testdata/src/typeparams/typeparams.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,69 @@ func GenericInterfaceAssertionTest[T io.Reader]() {
3434
default:
3535
}
3636
}
37+
38+
// Issue 50658: Check for type parameters in type switches.
39+
type Float interface {
40+
float32 | float64
41+
}
42+
43+
type Doer[F Float] interface {
44+
Do() F
45+
}
46+
47+
func Underlying[F Float](v Doer[F]) string {
48+
switch v.(type) {
49+
case Doer[float32]:
50+
return "float32!"
51+
case Doer[float64]:
52+
return "float64!"
53+
default:
54+
return "<unknown>"
55+
}
56+
}
57+
58+
func DoIf[F Float]() {
59+
// This is a synthetic function to create a non-generic to generic assignment.
60+
// This function does not make much sense.
61+
var v Doer[float32]
62+
if t, ok := v.(Doer[F]); ok {
63+
t.Do()
64+
}
65+
}
66+
67+
func IsASwitch[F Float, U Float](v Doer[F]) bool {
68+
switch v.(type) {
69+
case Doer[U]:
70+
return true
71+
}
72+
return false
73+
}
74+
75+
func IsA[F Float, U Float](v Doer[F]) bool {
76+
_, is := v.(Doer[U])
77+
return is
78+
}
79+
80+
func LayeredTypes[F Float]() {
81+
// This is a synthetic function cover more isParameterized cases.
82+
type T interface {
83+
foo() struct{ _ map[T][2]chan *F }
84+
}
85+
type V interface {
86+
foo() struct{ _ map[T][2]chan *float32 }
87+
}
88+
var t T
89+
var v V
90+
t, _ = v.(T)
91+
_ = t
92+
}
93+
94+
type X[T any] struct{}
95+
96+
func (x X[T]) m(T) {}
97+
98+
func InstancesOfGenericMethods() {
99+
var x interface{ m(string) }
100+
// _ = x.(X[int]) // BAD. Not enabled as it does not type check.
101+
_ = x.(X[string]) // OK
102+
}

0 commit comments

Comments
 (0)