Skip to content

Commit 074820e

Browse files
committed
go/analysis/passes/usesgenerics: a new analysis to detect generic code
Add a new "usesgenerics" analyzer to report uses of features related to generic programming. This analyzer may be used to temporarily guard other analyzers until they may be updated to support generic features. Along the way, update the typeparams API to return the Info.Instances map, rather than provide indirect access, so that it can be iterated. Fixes golang/go#48790 Change-Id: Ia3555524beff6e19f0b9101582205e26e757c8da Reviewed-on: https://go-review.googlesource.com/c/tools/+/355009 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 94178a2 commit 074820e

File tree

10 files changed

+252
-17
lines changed

10 files changed

+252
-17
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// want package:`features{typeDecl,funcDecl,funcInstance}`
2+
3+
package a
4+
5+
type T[P any] int
6+
7+
func F[P any]() {}
8+
9+
var _ = F[int]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// want package:`features{typeSet}`
2+
3+
package b
4+
5+
type Constraint interface {
6+
~int | string
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// want package:`features{typeDecl,funcDecl,typeSet,typeInstance,funcInstance}`
2+
3+
// Features funcDecl, typeSet, and funcInstance come from imported packages "a"
4+
// and "b". These features are not directly present in "c".
5+
6+
package c
7+
8+
import (
9+
"a"
10+
"b"
11+
)
12+
13+
type T[P b.Constraint] a.T[P]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// want package:`features{typeSet}`
2+
3+
package d
4+
5+
type myInt int
6+
7+
func _() {
8+
// Sanity check that we can both detect local types and interfaces with
9+
// embedded defined types.
10+
type constraint interface {
11+
myInt
12+
}
13+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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 usesgenerics defines an Analyzer that checks for usage of generic
6+
// features added in Go 1.18.
7+
package usesgenerics
8+
9+
import (
10+
"go/ast"
11+
"go/types"
12+
"reflect"
13+
"strings"
14+
15+
"golang.org/x/tools/go/analysis"
16+
"golang.org/x/tools/go/analysis/passes/inspect"
17+
"golang.org/x/tools/go/ast/inspector"
18+
"golang.org/x/tools/internal/typeparams"
19+
)
20+
21+
var Analyzer = &analysis.Analyzer{
22+
Name: "usesgenerics",
23+
Doc: Doc,
24+
Requires: []*analysis.Analyzer{inspect.Analyzer},
25+
Run: run,
26+
ResultType: reflect.TypeOf((*Result)(nil)),
27+
FactTypes: []analysis.Fact{new(featuresFact)},
28+
}
29+
30+
const Doc = `detect whether a package uses generics features
31+
32+
The usesgenerics analysis reports whether a package directly or transitively
33+
uses certain features associated with generic programming in Go.`
34+
35+
// Result is the usesgenerics analyzer result type. The Direct field records
36+
// features used directly by the package being analyzed (i.e. contained in the
37+
// package source code). The Transitive field records any features used by the
38+
// package or any of its transitive imports.
39+
type Result struct {
40+
Direct, Transitive Features
41+
}
42+
43+
// Features is a set of flags reporting which features of generic Go code a
44+
// package uses, or 0.
45+
type Features int
46+
47+
const (
48+
// GenericTypeDecls indicates whether the package declares types with type
49+
// parameters.
50+
GenericTypeDecls Features = 1 << iota
51+
52+
// GenericFuncDecls indicates whether the package declares functions with
53+
// type parameters.
54+
GenericFuncDecls
55+
56+
// EmbeddedTypeSets indicates whether the package declares interfaces that
57+
// contain structural type restrictions, i.e. are not fully described by
58+
// their method sets.
59+
EmbeddedTypeSets
60+
61+
// TypeInstantiation indicates whether the package instantiates any generic
62+
// types.
63+
TypeInstantiation
64+
65+
// FuncInstantiation indicates whether the package instantiates any generic
66+
// functions.
67+
FuncInstantiation
68+
)
69+
70+
func (f Features) String() string {
71+
var feats []string
72+
if f&GenericTypeDecls != 0 {
73+
feats = append(feats, "typeDecl")
74+
}
75+
if f&GenericFuncDecls != 0 {
76+
feats = append(feats, "funcDecl")
77+
}
78+
if f&EmbeddedTypeSets != 0 {
79+
feats = append(feats, "typeSet")
80+
}
81+
if f&TypeInstantiation != 0 {
82+
feats = append(feats, "typeInstance")
83+
}
84+
if f&FuncInstantiation != 0 {
85+
feats = append(feats, "funcInstance")
86+
}
87+
return "features{" + strings.Join(feats, ",") + "}"
88+
}
89+
90+
type featuresFact struct {
91+
Features Features
92+
}
93+
94+
func (f *featuresFact) AFact() {}
95+
func (f *featuresFact) String() string { return f.Features.String() }
96+
97+
func run(pass *analysis.Pass) (interface{}, error) {
98+
direct := directFeatures(pass)
99+
100+
transitive := direct | importedTransitiveFeatures(pass)
101+
if transitive != 0 {
102+
pass.ExportPackageFact(&featuresFact{transitive})
103+
}
104+
105+
return &Result{
106+
Direct: direct,
107+
Transitive: transitive,
108+
}, nil
109+
}
110+
111+
// directFeatures computes which generic features are used directly by the
112+
// package being analyzed.
113+
func directFeatures(pass *analysis.Pass) Features {
114+
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
115+
116+
nodeFilter := []ast.Node{
117+
(*ast.FuncType)(nil),
118+
(*ast.InterfaceType)(nil),
119+
(*ast.ImportSpec)(nil),
120+
(*ast.TypeSpec)(nil),
121+
}
122+
123+
var direct Features
124+
125+
inspect.Preorder(nodeFilter, func(node ast.Node) {
126+
switch n := node.(type) {
127+
case *ast.FuncType:
128+
if tparams := typeparams.ForFuncType(n); tparams != nil {
129+
direct |= GenericFuncDecls
130+
}
131+
case *ast.InterfaceType:
132+
tv := pass.TypesInfo.Types[n]
133+
if iface, _ := tv.Type.(*types.Interface); iface != nil && !typeparams.IsMethodSet(iface) {
134+
direct |= EmbeddedTypeSets
135+
}
136+
case *ast.TypeSpec:
137+
if tparams := typeparams.ForTypeSpec(n); tparams != nil {
138+
direct |= GenericTypeDecls
139+
}
140+
}
141+
})
142+
143+
instances := typeparams.GetInstances(pass.TypesInfo)
144+
for _, inst := range instances {
145+
switch inst.Type.(type) {
146+
case *types.Named:
147+
direct |= TypeInstantiation
148+
case *types.Signature:
149+
direct |= FuncInstantiation
150+
}
151+
}
152+
return direct
153+
}
154+
155+
// importedTransitiveFeatures computes features that are used transitively via
156+
// imports.
157+
func importedTransitiveFeatures(pass *analysis.Pass) Features {
158+
var feats Features
159+
for _, imp := range pass.Pkg.Imports() {
160+
var importedFact featuresFact
161+
if pass.ImportPackageFact(imp, &importedFact) {
162+
feats |= importedFact.Features
163+
}
164+
}
165+
return feats
166+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 usesgenerics_test
6+
7+
import (
8+
"testing"
9+
10+
"golang.org/x/tools/go/analysis/analysistest"
11+
"golang.org/x/tools/go/analysis/passes/usesgenerics"
12+
"golang.org/x/tools/internal/typeparams"
13+
)
14+
15+
func Test(t *testing.T) {
16+
if !typeparams.Enabled {
17+
t.Skip("type parameters are not enabled at this Go version")
18+
}
19+
testdata := analysistest.TestData()
20+
analysistest.Run(t, testdata, usesgenerics.Analyzer, "a", "b", "c", "d")
21+
}

internal/lsp/analysis/infertypeargs/run_go118.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ func run(pass *analysis.Pass) (interface{}, error) {
3333
}
3434

3535
// Confirm that instantiation actually occurred at this ident.
36-
_, instance := typeparams.GetInstance(pass.TypesInfo, ident)
37-
if instance == nil {
36+
idata, ok := typeparams.GetInstances(pass.TypesInfo)[ident]
37+
if !ok {
3838
return // something went wrong, but fail open
3939
}
40+
instance := idata.Type
4041

4142
// Start removing argument expressions from the right, and check if we can
4243
// still infer the call expression.
@@ -62,7 +63,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
6263
// Most likely inference failed.
6364
break
6465
}
65-
_, newInstance := typeparams.GetInstance(info, ident)
66+
newIData := typeparams.GetInstances(info)[ident]
67+
newInstance := newIData.Type
6668
if !types.Identical(instance, newInstance) {
6769
// The inferred result type does not match the original result type, so
6870
// this simplification is not valid.

internal/lsp/source/identifier.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,8 @@ func fullNode(snapshot Snapshot, obj types.Object, pkg Package) (ast.Decl, error
354354
//
355355
// If no such signature exists, it returns nil.
356356
func inferredSignature(info *types.Info, id *ast.Ident) *types.Signature {
357-
_, typ := typeparams.GetInstance(info, id)
358-
sig, _ := typ.(*types.Signature)
357+
inst := typeparams.GetInstances(info)[id]
358+
sig, _ := inst.Type.(*types.Signature)
359359
return sig
360360
}
361361

internal/typeparams/typeparams_go117.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,16 @@ func NewUnion(terms []*Term) *Union {
186186
// InitInstanceInfo is a noop at this Go version.
187187
func InitInstanceInfo(*types.Info) {}
188188

189-
// GetInstance returns nothing, as type parameters are not supported at this Go
190-
// version.
191-
func GetInstance(*types.Info, *ast.Ident) (*TypeList, types.Type) { return nil, nil }
189+
// Instance is a placeholder type, as type parameters are not supported at this
190+
// Go version.
191+
type Instance struct {
192+
TypeArgs *TypeList
193+
Type types.Type
194+
}
195+
196+
// GetInstances returns a nil map, as type parameters are not supported at this
197+
// Go version.
198+
func GetInstances(info *types.Info) map[*ast.Ident]Instance { return nil }
192199

193200
// Context is a placeholder type, as type parameters are not supported at
194201
// this Go version.

internal/typeparams/typeparams_go118.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,15 +169,12 @@ func InitInstanceInfo(info *types.Info) {
169169
info.Instances = make(map[*ast.Ident]types.Instance)
170170
}
171171

172-
// GetInstance extracts information about the instantiation occurring at the
173-
// identifier id. id should be the identifier denoting a parameterized type or
174-
// function in an instantiation expression or function call.
175-
func GetInstance(info *types.Info, id *ast.Ident) (*TypeList, types.Type) {
176-
if info.Instances != nil {
177-
inf := info.Instances[id]
178-
return inf.TypeArgs, inf.Type
179-
}
180-
return nil, nil
172+
// Instance is an alias for types.Instance.
173+
type Instance = types.Instance
174+
175+
// GetInstances returns info.Instances.
176+
func GetInstances(info *types.Info) map[*ast.Ident]Instance {
177+
return info.Instances
181178
}
182179

183180
// Context is an alias for types.Context.

0 commit comments

Comments
 (0)