Skip to content

Commit

Permalink
account for anonymous interface in type params
Browse files Browse the repository at this point in the history
Previously the typeparams logic did not account for anonymous interfaces
in type paramters. Fix this, and add tests.

A bit of housekeeping to ease debugging: The types and funcs in the
testdata typeparams package are moved to separate files. This should
ease debugging. To debug a problematic test case in isolation, the
single function for the test case can be added to a separate new file
(say funcs_debug.go), and the existing funcs in funcs.go can be disabled
altogether by setting build constraint in funcs.go to "go118 && never",
such that it isn't included.

Also, move the check for same basic kind-ness to once at the end.
  • Loading branch information
nishanths committed Nov 27, 2022
1 parent 228d3b5 commit fb9eaeb
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 92 deletions.
68 changes: 38 additions & 30 deletions common_go118.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,8 @@ func fromNamed(pass *analysis.Pass, t *types.Named, typeparam bool) (result []ty
}

func fromInterface(pass *analysis.Pass, intf *types.Interface, typeparam bool) (result []typeAndMembers, all bool) {
var kind types.BasicKind
var kindSet bool
all = true

// sameKind reports whether each type t that the function is called with
// has the same underlying basic kind as the rest.
sameBasicKind := func(t types.Type) (ok bool) {
basic, ok := t.Underlying().(*types.Basic)
if !ok {
return false
}
if kindSet && kind != basic.Kind() {
return false
}
kind = basic.Kind()
kindSet = true
return true
}

for i := 0; i < intf.NumEmbeddeds(); i++ {
embed := intf.EmbeddedType(i)

Expand All @@ -60,24 +43,12 @@ func fromInterface(pass *analysis.Pass, intf *types.Interface, typeparam bool) (
// gather from each term in the union.
for i := 0; i < u.Len(); i++ {
r, a := fromType(pass, u.Term(i).Type(), typeparam)
for _, rr := range r {
if !sameBasicKind(rr.et.TypeName.Type()) {
a = false
break
}
}
result = append(result, r...)
all = all && a
}

case *types.Named:
r, a := fromNamed(pass, embed.(*types.Named), typeparam)
for _, rr := range r {
if !sameBasicKind(rr.et.TypeName.Type()) {
a = false
break
}
}
result = append(result, r...)
all = all && a

Expand All @@ -102,6 +73,14 @@ func fromType(pass *analysis.Pass, t types.Type, typeparam bool) (result []typeA
intf := t.Constraint().Underlying().(*types.Interface)
return fromInterface(pass, intf, typeparam)

case *types.Interface:
// anonymous interface.
// e.g. func foo[T interface { M } | interface { N }](v T) {}
if !typeparam {
return nil, true
}
return fromInterface(pass, t, typeparam)

default:
// ignore these.
return nil, true
Expand All @@ -110,5 +89,34 @@ func fromType(pass *analysis.Pass, t types.Type, typeparam bool) (result []typeA

func composingEnumTypes(pass *analysis.Pass, t types.Type) (result []typeAndMembers, ok bool) {
_, typeparam := t.(*types.TypeParam)
return fromType(pass, t, typeparam)
result, ok = fromType(pass, t, typeparam)

if typeparam {
var kind types.BasicKind
var kindSet bool

// sameKind reports whether each type t that the function is called
// with has the same underlying basic kind.
sameBasicKind := func(t types.Type) (ok bool) {
basic, ok := t.Underlying().(*types.Basic)
if !ok {
return false
}
if kindSet && kind != basic.Kind() {
return false
}
kind = basic.Kind()
kindSet = true
return true
}

for _, rr := range result {
if !sameBasicKind(rr.et.TypeName.Type()) {
ok = false
break
}
}
}

return result, ok
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,67 +3,7 @@

package typeparam

import (
y "general/y"
)

type M uint8 // want M:"^A,B$"
const (
_ M = iota * 100
A
B
)

func (M) String() string { return "" }

type N uint8 // want N:"^C,D$"
const (
_ N = iota * 100
C
D
)

type O byte // want O:"^E1,E2$"
const (
E1 O = 'h'
E2 O = 'e'
)

type P float32 // want P:"^F$"
const (
F P = 1.1234
)

type Q string // want Q:"^G$"
const (
G Q = "world"
)

type NotEnumType uint8

type Stringer interface {
String() string
}

type II interface{ N | JJ }
type JJ interface{ O }
type KK interface {
M
Stringer
error
comparable
}
type LL interface {
M | NotEnumType
Stringer
error
}
type MM interface {
M
}
type QQ interface {
Q
}
import y "general/y"

func _a[T y.Phylum | M](v T) {
switch v { // want `^missing cases in switch of type bar.Phylum\|typeparam.M: bar.Chordata, bar.Mollusca, typeparam.B$`
Expand Down Expand Up @@ -93,7 +33,17 @@ func _b[T N | MM](v T) {
}
}

func _c[T O | M | N](v T) {
func _c0[T O | M | N](v T) {
switch v { // want `^missing cases in switch of type typeparam.O\|typeparam.M\|typeparam.N: typeparam.E1, typeparam.E2, typeparam.A\|typeparam.C$`
case T(B):
}

_ = map[T]struct{}{ // want `^missing keys in map of key type typeparam.O\|typeparam.M\|typeparam.N: typeparam.E1, typeparam.E2, typeparam.A\|typeparam.C$`
T(B): struct{}{},
}
}

func _c2[T interface{ O } | M | interface{ N }](v T) {
switch v { // want `^missing cases in switch of type typeparam.O\|typeparam.M\|typeparam.N: typeparam.E1, typeparam.E2, typeparam.A\|typeparam.C$`
case T(B):
}
Expand Down Expand Up @@ -125,6 +75,16 @@ func _e[T M](v T) {
}
}

func _f[T Anon](v T) {
switch v { // want `^missing cases in switch of type typeparam.M\|typeparam.N: typeparam.B\|typeparam.D$`
case T(C):
}

_ = map[T]struct{}{ // want `^missing keys in map of key type typeparam.M\|typeparam.N: typeparam.B\|typeparam.D$`
T(C): struct{}{},
}
}

func repeat0[T II | O](v T) {
switch v { // want `^missing cases in switch of type typeparam.N\|typeparam.O: typeparam.C, typeparam.D, typeparam.E2$`
case T(E1):
Expand All @@ -145,6 +105,16 @@ func repeat1[T MM | M](v T) {
}
}

func repeat2[T interface{ M } | interface{ M }](v T) {
switch v { // want `^missing cases in switch of type typeparam.M: typeparam.A$`
case T(B):
}

_ = map[T]struct{}{ // want `^missing keys in map of key type typeparam.M: typeparam.A$`
T(B): struct{}{},
}
}

func _mixedTypes0[T M | QQ](v T) {
// expect no diagnostic because underlying basic kinds are not same:
// uint8 vs. string
Expand All @@ -167,6 +137,26 @@ func _mixedTypes1[T MM | QQ](v T) {
}
}

func _mixedTypes2[T interface{ M } | interface{ Q }](v T) {
switch v {
case T(A):
}

_ = map[T]struct{}{
T(A): struct{}{},
}
}

func _mixedTypes3[T interface{ M | Q }](v T) {
switch v {
case T(A):
}

_ = map[T]struct{}{
T(A): struct{}{},
}
}

func _notEnumType0[T M | NotEnumType](v T) {
// expect no diagnostic because not type elements are enum types.
switch v {
Expand Down
65 changes: 65 additions & 0 deletions testdata/src/typeparam/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//go:build go1.18
// +build go1.18

package typeparam

type Stringer interface {
String() string
}

type M uint8 // want M:"^A,B$"
const (
_ M = iota * 100
A
B
)

func (M) String() string { return "" }

type N uint8 // want N:"^C,D$"
const (
_ N = iota * 100
C
D
)

type O byte // want O:"^E1,E2$"
const (
E1 O = 'h'
E2 O = 'e'
)

type P float32 // want P:"^F$"
const (
F P = 1.1234
)

type Q string // want Q:"^G$"
const (
G Q = "world"
)

type NotEnumType uint8

type II interface{ N | JJ }
type JJ interface{ O }
type KK interface {
M
Stringer
error
comparable
}
type LL interface {
M | NotEnumType
Stringer
error
}
type MM interface {
M
}
type Anon interface {
interface{ M } | interface{ N }
}
type QQ interface {
Q
}

0 comments on commit fb9eaeb

Please sign in to comment.