Skip to content

Commit 1050b5c

Browse files
committed
go/analysis/passes/composite: update for generics
Warn about unkeyed composite literals via literals of type parameter type. Updates golang/go#48704 Change-Id: Ia75139b56cb73288c133bd547d71664464015813 Reviewed-on: https://go-review.googlesource.com/c/tools/+/357756 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Tim King <[email protected]> TryBot-Result: Go Bot <[email protected]>
1 parent 5a40697 commit 1050b5c

File tree

6 files changed

+137
-37
lines changed

6 files changed

+137
-37
lines changed

Diff for: go/analysis/passes/composite/composite.go

+40-26
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"golang.org/x/tools/go/analysis"
1515
"golang.org/x/tools/go/analysis/passes/inspect"
1616
"golang.org/x/tools/go/ast/inspector"
17+
"golang.org/x/tools/internal/typeparams"
1718
)
1819

1920
const Doc = `check for unkeyed composite literals
@@ -67,41 +68,52 @@ func run(pass *analysis.Pass) (interface{}, error) {
6768
// skip whitelisted types
6869
return
6970
}
70-
under := typ.Underlying()
71-
for {
72-
ptr, ok := under.(*types.Pointer)
73-
if !ok {
74-
break
75-
}
76-
under = ptr.Elem().Underlying()
77-
}
78-
if _, ok := under.(*types.Struct); !ok {
79-
// skip non-struct composite literals
80-
return
81-
}
82-
if isLocalType(pass, typ) {
83-
// allow unkeyed locally defined composite literal
84-
return
71+
terms, err := typeparams.StructuralTerms(typ)
72+
if err != nil {
73+
return // invalid type
8574
}
75+
for _, term := range terms {
76+
under := deref(term.Type().Underlying())
77+
if _, ok := under.(*types.Struct); !ok {
78+
// skip non-struct composite literals
79+
continue
80+
}
81+
if isLocalType(pass, term.Type()) {
82+
// allow unkeyed locally defined composite literal
83+
continue
84+
}
8685

87-
// check if the CompositeLit contains an unkeyed field
88-
allKeyValue := true
89-
for _, e := range cl.Elts {
90-
if _, ok := e.(*ast.KeyValueExpr); !ok {
91-
allKeyValue = false
92-
break
86+
// check if the CompositeLit contains an unkeyed field
87+
allKeyValue := true
88+
for _, e := range cl.Elts {
89+
if _, ok := e.(*ast.KeyValueExpr); !ok {
90+
allKeyValue = false
91+
break
92+
}
9393
}
94-
}
95-
if allKeyValue {
96-
// all the composite literal fields are keyed
94+
if allKeyValue {
95+
// all the composite literal fields are keyed
96+
continue
97+
}
98+
99+
pass.ReportRangef(cl, "%s composite literal uses unkeyed fields", typeName)
97100
return
98101
}
99-
100-
pass.ReportRangef(cl, "%s composite literal uses unkeyed fields", typeName)
101102
})
102103
return nil, nil
103104
}
104105

106+
func deref(typ types.Type) types.Type {
107+
for {
108+
ptr, ok := typ.(*types.Pointer)
109+
if !ok {
110+
break
111+
}
112+
typ = ptr.Elem().Underlying()
113+
}
114+
return typ
115+
}
116+
105117
func isLocalType(pass *analysis.Pass, typ types.Type) bool {
106118
switch x := typ.(type) {
107119
case *types.Struct:
@@ -112,6 +124,8 @@ func isLocalType(pass *analysis.Pass, typ types.Type) bool {
112124
case *types.Named:
113125
// names in package foo are local to foo_test too
114126
return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
127+
case *typeparams.TypeParam:
128+
return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
115129
}
116130
return false
117131
}

Diff for: go/analysis/passes/composite/composite_test.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@ import (
99

1010
"golang.org/x/tools/go/analysis/analysistest"
1111
"golang.org/x/tools/go/analysis/passes/composite"
12+
"golang.org/x/tools/internal/typeparams"
1213
)
1314

1415
func Test(t *testing.T) {
1516
testdata := analysistest.TestData()
16-
analysistest.Run(t, testdata, composite.Analyzer, "a")
17+
pkgs := []string{"a"}
18+
if typeparams.Enabled {
19+
pkgs = append(pkgs, "typeparams")
20+
}
21+
analysistest.Run(t, testdata, composite.Analyzer, pkgs...)
1722
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2021 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+
5+
package lib
6+
7+
type Struct struct{ F int }
8+
type Slice []int
9+
type Map map[int]int
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2021 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+
5+
package typeparams
6+
7+
import "typeparams/lib"
8+
9+
type localStruct struct { F int }
10+
11+
func F[
12+
T1 ~struct{ f int },
13+
T2a localStruct,
14+
T2b lib.Struct,
15+
T3 ~[]int,
16+
T4 lib.Slice,
17+
T5 ~map[int]int,
18+
T6 lib.Map,
19+
]() {
20+
_ = T1{2}
21+
_ = T2a{2}
22+
_ = T2b{2} // want "unkeyed fields"
23+
_ = T3{1,2}
24+
_ = T4{1,2}
25+
_ = T5{1:2}
26+
_ = T6{1:2}
27+
}

Diff for: internal/typeparams/normalize.go

+39
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package typeparams
66

77
import (
8+
"errors"
89
"fmt"
910
"go/types"
1011
"os"
@@ -65,6 +66,44 @@ func NormalizeInterface(iface *types.Interface) (*types.Interface, error) {
6566
return types.NewInterfaceType(methods, embeddeds), nil
6667
}
6768

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
104+
}
105+
}
106+
68107
// A termSet holds the normalized set of terms for a given type.
69108
//
70109
// The name termSet is intentionally distinct from 'type set': a type set is

Diff for: internal/typeparams/typeparams_go117.go

+16-10
Original file line numberDiff line numberDiff line change
@@ -164,19 +164,25 @@ func NamedTypeOrigin(named *types.Named) types.Type {
164164
return named
165165
}
166166

167-
// Term is a placeholder type, as type parameters are not supported at this Go
168-
// version. Its methods panic on use.
169-
type Term struct{}
170-
171-
func (*Term) Tilde() bool { unsupported(); return false }
172-
func (*Term) Type() types.Type { unsupported(); return nil }
173-
func (*Term) String() string { unsupported(); return "" }
174-
func (*Term) Underlying() types.Type { unsupported(); return nil }
167+
// Term holds information about a structural type restriction.
168+
type Term struct {
169+
tilde bool
170+
typ types.Type
171+
}
172+
173+
func (m *Term) Tilde() bool { return m.tilde }
174+
func (m *Term) Type() types.Type { return m.typ }
175+
func (m *Term) String() string {
176+
pre := ""
177+
if m.tilde {
178+
pre = "~"
179+
}
180+
return pre + m.typ.String()
181+
}
175182

176183
// NewTerm is unsupported at this Go version, and panics.
177184
func NewTerm(tilde bool, typ types.Type) *Term {
178-
unsupported()
179-
return nil
185+
return &Term{tilde, typ}
180186
}
181187

182188
// Union is a placeholder type, as type parameters are not supported at this Go

0 commit comments

Comments
 (0)