Skip to content

Commit 4adea50

Browse files
committed
internal/typeparams: normalize the underlying constraint interface
A call to Underlying() was missing from the computation of a type parameter's structural terms, resulting in incorrect results for type parameters with defined constraints. This wasn't caught because all analyzer tests use inlined interfaces out of convenience, and the fallback behavior for vet analyzers using this API is to fail silently. Fix this, and add tests that would have failed. Also update the normalization tests to use the StructuralTerms API, and delete the earlier NormalizeInterface API. StructuralTerms has proven to be more useful. For clarity, change the StructuralTerms function to accept a *TypeParam rather than arbitrary Type, and significantly revamp its docstring. Updates golang/go#49597 Change-Id: I8b863604655b44b943e6afbd5d22a66f44260d10 Reviewed-on: https://go-review.googlesource.com/c/tools/+/363982 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Tim King <[email protected]>
1 parent 4392381 commit 4adea50

File tree

6 files changed

+165
-125
lines changed

6 files changed

+165
-125
lines changed

go/analysis/passes/composite/composite.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,26 @@ func run(pass *analysis.Pass) (interface{}, error) {
6868
// skip whitelisted types
6969
return
7070
}
71-
terms, err := typeparams.StructuralTerms(typ)
72-
if err != nil {
73-
return // invalid type
71+
var structuralTypes []types.Type
72+
switch typ := typ.(type) {
73+
case *typeparams.TypeParam:
74+
terms, err := typeparams.StructuralTerms(typ)
75+
if err != nil {
76+
return // invalid type
77+
}
78+
for _, term := range terms {
79+
structuralTypes = append(structuralTypes, term.Type())
80+
}
81+
default:
82+
structuralTypes = append(structuralTypes, typ)
7483
}
75-
for _, term := range terms {
76-
under := deref(term.Type().Underlying())
84+
for _, typ := range structuralTypes {
85+
under := deref(typ.Underlying())
7786
if _, ok := under.(*types.Struct); !ok {
7887
// skip non-struct composite literals
7988
continue
8089
}
81-
if isLocalType(pass, term.Type()) {
90+
if isLocalType(pass, typ) {
8291
// allow unkeyed locally defined composite literal
8392
continue
8493
}

go/analysis/passes/printf/testdata/src/typeparams/diagnostics.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func TestBasicTypeParams[T interface{ ~int }, E error, F fmt.Formatter, S fmt.St
1313
fmt.Printf("%d", t)
1414
fmt.Printf("%s", t) // want "wrong type.*contains ~int"
1515
fmt.Printf("%v", t)
16-
fmt.Printf("%d", e)
16+
fmt.Printf("%d", e) // want "wrong type"
1717
fmt.Printf("%s", e)
1818
fmt.Errorf("%w", e)
1919
fmt.Printf("%a", f)
@@ -28,6 +28,15 @@ func TestBasicTypeParams[T interface{ ~int }, E error, F fmt.Formatter, S fmt.St
2828
fmt.Printf("%T", a)
2929
}
3030

31+
type Constraint interface {
32+
~int
33+
}
34+
35+
func TestNamedConstraints_Issue49597[T Constraint](t T) {
36+
fmt.Printf("%d", t)
37+
fmt.Printf("%s", t) // want "wrong type.*contains ~int"
38+
}
39+
3140
func TestNestedTypeParams[T interface{ ~int }, S interface{ ~string }]() {
3241
var x struct {
3342
f int
@@ -71,11 +80,14 @@ func TestRecusivePointers[T1 ~*T2, T2 ~*T1](t1 T1, t2 T2) {
7180
fmt.Printf("%s", t2)
7281
}
7382

74-
func TestEmptyTypeSet[T interface{ int | string; float64 }](t T) {
83+
func TestEmptyTypeSet[T interface {
84+
int | string
85+
float64
86+
}](t T) {
7587
fmt.Printf("%s", t) // No error: empty type set.
7688
}
7789

78-
func TestPointerRules[T ~*[]int|*[2]int](t T) {
90+
func TestPointerRules[T ~*[]int | *[2]int](t T) {
7991
var slicePtr *[]int
8092
var arrayPtr *[2]int
8193
fmt.Printf("%d", slicePtr)

go/analysis/passes/shift/shift.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"go/ast"
1515
"go/constant"
1616
"go/token"
17+
"go/types"
1718
"math"
1819

1920
"golang.org/x/tools/go/analysis"
@@ -95,13 +96,22 @@ func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) {
9596
if t == nil {
9697
return
9798
}
98-
terms, err := typeparams.StructuralTerms(t)
99-
if err != nil {
100-
return // invalid type
99+
var structuralTypes []types.Type
100+
switch t := t.(type) {
101+
case *typeparams.TypeParam:
102+
terms, err := typeparams.StructuralTerms(t)
103+
if err != nil {
104+
return // invalid type
105+
}
106+
for _, term := range terms {
107+
structuralTypes = append(structuralTypes, term.Type())
108+
}
109+
default:
110+
structuralTypes = append(structuralTypes, t)
101111
}
102112
sizes := make(map[int64]struct{})
103-
for _, term := range terms {
104-
size := 8 * pass.TypesSizes.Sizeof(term.Type())
113+
for _, t := range structuralTypes {
114+
size := 8 * pass.TypesSizes.Sizeof(t)
105115
sizes[size] = struct{}{}
106116
}
107117
minSize := int64(math.MaxInt64)

go/analysis/passes/stringintconv/string.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,17 +110,17 @@ func run(pass *analysis.Pass) (interface{}, error) {
110110

111111
// First, find a type T0 in T that has an underlying type of string.
112112
T := tname.Type()
113-
tterms, err := typeparams.StructuralTerms(T)
113+
ttypes, err := structuralTypes(T)
114114
if err != nil {
115115
return // invalid type
116116
}
117117

118118
var T0 types.Type // string type in the type set of T
119119

120-
for _, term := range tterms {
121-
u, _ := term.Type().Underlying().(*types.Basic)
120+
for _, tt := range ttypes {
121+
u, _ := tt.Underlying().(*types.Basic)
122122
if u != nil && u.Kind() == types.String {
123-
T0 = term.Type()
123+
T0 = tt
124124
break
125125
}
126126
}
@@ -133,21 +133,21 @@ func run(pass *analysis.Pass) (interface{}, error) {
133133
// Next, find a type V0 in V that has an underlying integral type that is
134134
// not byte or rune.
135135
V := pass.TypesInfo.TypeOf(arg)
136-
vterms, err := typeparams.StructuralTerms(V)
136+
vtypes, err := structuralTypes(V)
137137
if err != nil {
138138
return // invalid type
139139
}
140140

141141
var V0 types.Type // integral type in the type set of V
142142

143-
for _, term := range vterms {
144-
u, _ := term.Type().Underlying().(*types.Basic)
143+
for _, vt := range vtypes {
144+
u, _ := vt.Underlying().(*types.Basic)
145145
if u != nil && u.Info()&types.IsInteger != 0 {
146146
switch u.Kind() {
147147
case types.Byte, types.Rune, types.UntypedRune:
148148
continue
149149
}
150-
V0 = term.Type()
150+
V0 = vt
151151
break
152152
}
153153
}
@@ -158,8 +158,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
158158
}
159159

160160
convertibleToRune := true // if true, we can suggest a fix
161-
for _, term := range vterms {
162-
if !types.ConvertibleTo(term.Type(), types.Typ[types.Rune]) {
161+
for _, t := range vtypes {
162+
if !types.ConvertibleTo(t, types.Typ[types.Rune]) {
163163
convertibleToRune = false
164164
break
165165
}
@@ -200,3 +200,20 @@ func run(pass *analysis.Pass) (interface{}, error) {
200200
})
201201
return nil, nil
202202
}
203+
204+
func structuralTypes(t types.Type) ([]types.Type, error) {
205+
var structuralTypes []types.Type
206+
switch t := t.(type) {
207+
case *typeparams.TypeParam:
208+
terms, err := typeparams.StructuralTerms(t)
209+
if err != nil {
210+
return nil, err
211+
}
212+
for _, term := range terms {
213+
structuralTypes = append(structuralTypes, term.Type())
214+
}
215+
default:
216+
structuralTypes = append(structuralTypes, t)
217+
}
218+
return structuralTypes, nil
219+
}

internal/typeparams/normalize.go

Lines changed: 56 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -16,92 +16,72 @@ import (
1616

1717
const debug = false
1818

19-
// NormalizeInterface returns the normal form of the interface iface, or nil if iface
20-
// has an empty type set (i.e. there are no types that satisfy iface). If the
21-
// resulting interface is non-nil, it will be identical to iface.
19+
var ErrEmptyTypeSet = errors.New("empty type set")
20+
21+
// StructuralTerms returns a slice of terms representing the normalized
22+
// structural type restrictions of a type parameter, if any.
23+
//
24+
// Structural type restrictions of a type parameter are created via
25+
// non-interface types embedded in its constraint interface (directly, or via a
26+
// chain of interface embeddings). For example, in the declaration `type T[P
27+
// interface{~int; m()}] int`, the structural restriction of the type parameter
28+
// P is ~int.
29+
//
30+
// With interface embedding and unions, the specification of structural type
31+
// restrictions may be arbitrarily complex. For example, consider the
32+
// following:
33+
//
34+
// type A interface{ ~string|~[]byte }
35+
//
36+
// type B interface{ int|string }
37+
//
38+
// type C interface { ~string|~int }
39+
//
40+
// type T[P interface{ A|B; C }] int
2241
//
23-
// An error is returned if the interface type is invalid, or too complicated to
24-
// reasonably normalize (for example, contains unions with more than a hundred
25-
// terms).
42+
// In this example, the structural type restriction of P is ~string|int: A|B
43+
// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int,
44+
// which when intersected with C (~string|~int) yields ~string|int.
2645
//
27-
// An interface is in normal form if and only if:
28-
// - it has 0 or 1 embedded types.
29-
// - its embedded type is either a types.Union or has a concrete
30-
// (non-interface) underlying type
31-
// - if the embedded type is a union, each term of the union has a concrete
32-
// underlying type, and no terms may be removed without changing the type set
33-
// of the interface
34-
func NormalizeInterface(iface *types.Interface) (*types.Interface, error) {
35-
var methods []*types.Func
36-
for i := 0; i < iface.NumMethods(); i++ {
37-
methods = append(methods, iface.Method(i))
46+
// StructuralTerms computes these expansions and reductions, producing a
47+
// "normalized" form of the embeddings. A structural restriction is normalized
48+
// if it is a single union containing no interface terms, and is minimal in the
49+
// sense that removing any term changes the set of types satisfying the
50+
// constraint. It is left as a proof for the reader that, modulo sorting, there
51+
// is exactly one such normalized form.
52+
//
53+
// Because the minimal representation always takes this form, StructuralTerms
54+
// returns a slice of tilde terms corresponding to the terms of the union in
55+
// the normalized structural restriction. An error is returned if the
56+
// constraint interface is invalid, exceeds complexity bounds, or has an empty
57+
// type set. In the latter case, StructuralTerms returns ErrEmptyTypeSet.
58+
//
59+
// StructuralTerms makes no guarantees about the order of terms, except that it
60+
// is deterministic.
61+
func StructuralTerms(tparam *TypeParam) ([]*Term, error) {
62+
constraint := tparam.Constraint()
63+
if constraint == nil {
64+
return nil, fmt.Errorf("%s has nil constraint", tparam)
65+
}
66+
iface, _ := constraint.Underlying().(*types.Interface)
67+
if iface == nil {
68+
return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying())
3869
}
39-
var embeddeds []types.Type
4070
tset, err := computeTermSet(iface, make(map[types.Type]*termSet), 0)
4171
if err != nil {
4272
return nil, err
4373
}
44-
switch {
45-
case tset.terms.isEmpty():
46-
// Special case: as documented
74+
if tset.terms.isEmpty() {
75+
return nil, ErrEmptyTypeSet
76+
}
77+
if tset.terms.isAll() {
4778
return nil, nil
48-
49-
case tset.terms.isAll():
50-
// No embeddeds.
51-
52-
case len(tset.terms) == 1:
53-
if !tset.terms[0].tilde {
54-
embeddeds = append(embeddeds, tset.terms[0].typ)
55-
break
56-
}
57-
fallthrough
58-
default:
59-
var terms []*Term
60-
for _, term := range tset.terms {
61-
terms = append(terms, NewTerm(term.tilde, term.typ))
62-
}
63-
embeddeds = append(embeddeds, NewUnion(terms))
6479
}
65-
66-
return types.NewInterfaceType(methods, embeddeds), nil
67-
}
68-
69-
var ErrEmptyTypeSet = errors.New("empty type set")
70-
71-
// StructuralTerms returns the normalized structural type restrictions of a
72-
// type, if any. For types that are not type parameters, it returns term slice
73-
// containing a single non-tilde term holding the given type. For type
74-
// parameters, it returns the normalized term list of the type parameter's
75-
// constraint. See NormalizeInterface for more information on the normal form
76-
// of a constraint interface.
77-
//
78-
// StructuralTerms returns an error if the structural term list cannot be
79-
// computed. If the type set of typ is empty, it returns ErrEmptyTypeSet.
80-
func StructuralTerms(typ types.Type) ([]*Term, error) {
81-
switch typ := typ.(type) {
82-
case *TypeParam:
83-
iface, _ := typ.Constraint().(*types.Interface)
84-
if iface == nil {
85-
return nil, fmt.Errorf("constraint is %T, not *types.Interface", typ)
86-
}
87-
tset, err := computeTermSet(iface, make(map[types.Type]*termSet), 0)
88-
if err != nil {
89-
return nil, err
90-
}
91-
if tset.terms.isEmpty() {
92-
return nil, ErrEmptyTypeSet
93-
}
94-
if tset.terms.isAll() {
95-
return nil, nil
96-
}
97-
var terms []*Term
98-
for _, term := range tset.terms {
99-
terms = append(terms, NewTerm(term.tilde, term.typ))
100-
}
101-
return terms, nil
102-
default:
103-
return []*Term{NewTerm(false, typ)}, nil
80+
var terms []*Term
81+
for _, term := range tset.terms {
82+
terms = append(terms, NewTerm(term.tilde, term.typ))
10483
}
84+
return terms, nil
10585
}
10686

10787
// A termSet holds the normalized set of terms for a given type.

0 commit comments

Comments
 (0)