Skip to content

Commit bcc6fa8

Browse files
committed
internal/typeparams: guard against generics in stdlib tests
Add a new package internal/typeparams/genericfeatures, factoring out the check for usage of generics from the usesgenerics checker. For tests operating on the standard library, this allows us to explicitly detect usage of generics, rather than relying on a hard-coded list. To make this work, I switched the go/ssa standard library test to use go/packages rather than go/loader. This resulted in finding 457 packages versus 676 (likely due to tests), but my assumption is that this is still enough coverage. Change-Id: I6ba32fb4068dd7f5eedb55c2081c642665ed124a Reviewed-on: https://go-review.googlesource.com/c/tools/+/355972 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 fc8b4ca commit bcc6fa8

File tree

6 files changed

+179
-138
lines changed

6 files changed

+179
-138
lines changed

go/analysis/passes/usesgenerics/usesgenerics.go

Lines changed: 14 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,12 @@
77
package usesgenerics
88

99
import (
10-
"go/ast"
11-
"go/types"
1210
"reflect"
13-
"strings"
1411

1512
"golang.org/x/tools/go/analysis"
1613
"golang.org/x/tools/go/analysis/passes/inspect"
1714
"golang.org/x/tools/go/ast/inspector"
18-
"golang.org/x/tools/internal/typeparams"
15+
"golang.org/x/tools/internal/typeparams/genericfeatures"
1916
)
2017

2118
var Analyzer = &analysis.Analyzer{
@@ -32,6 +29,16 @@ const Doc = `detect whether a package uses generics features
3229
The usesgenerics analysis reports whether a package directly or transitively
3330
uses certain features associated with generic programming in Go.`
3431

32+
type Features = genericfeatures.Features
33+
34+
const (
35+
GenericTypeDecls = genericfeatures.GenericTypeDecls
36+
GenericFuncDecls = genericfeatures.GenericFuncDecls
37+
EmbeddedTypeSets = genericfeatures.EmbeddedTypeSets
38+
TypeInstantiation = genericfeatures.TypeInstantiation
39+
FuncInstantiation = genericfeatures.FuncInstantiation
40+
)
41+
3542
// Result is the usesgenerics analyzer result type. The Direct field records
3643
// features used directly by the package being analyzed (i.e. contained in the
3744
// package source code). The Transitive field records any features used by the
@@ -40,53 +47,6 @@ type Result struct {
4047
Direct, Transitive Features
4148
}
4249

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-
9050
type featuresFact struct {
9151
Features Features
9252
}
@@ -95,7 +55,9 @@ func (f *featuresFact) AFact() {}
9555
func (f *featuresFact) String() string { return f.Features.String() }
9656

9757
func run(pass *analysis.Pass) (interface{}, error) {
98-
direct := directFeatures(pass)
58+
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
59+
60+
direct := genericfeatures.ForPackage(inspect, pass.TypesInfo)
9961

10062
transitive := direct | importedTransitiveFeatures(pass)
10163
if transitive != 0 {
@@ -108,50 +70,6 @@ func run(pass *analysis.Pass) (interface{}, error) {
10870
}, nil
10971
}
11072

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-
15573
// importedTransitiveFeatures computes features that are used transitively via
15674
// imports.
15775
func importedTransitiveFeatures(pass *analysis.Pass) Features {

go/internal/gcimporter/bexport_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ import (
1919
"strings"
2020
"testing"
2121

22+
"golang.org/x/tools/go/ast/inspector"
2223
"golang.org/x/tools/go/buildutil"
2324
"golang.org/x/tools/go/internal/gcimporter"
2425
"golang.org/x/tools/go/loader"
2526
"golang.org/x/tools/internal/typeparams"
27+
"golang.org/x/tools/internal/typeparams/genericfeatures"
2628
)
2729

2830
var isRace = false
@@ -66,14 +68,22 @@ type UnknownType undefined
6668
}
6769

6870
numPkgs := len(prog.AllPackages)
69-
if want := 248; numPkgs < want {
71+
if want := minStdlibPackages; numPkgs < want {
7072
t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
7173
}
7274

75+
checked := 0
7376
for pkg, info := range prog.AllPackages {
7477
if info.Files == nil {
7578
continue // empty directory
7679
}
80+
// Binary export does not support generic code.
81+
inspect := inspector.New(info.Files)
82+
if genericfeatures.ForPackage(inspect, &info.Info) != 0 {
83+
t.Logf("skipping package %q which uses generics", pkg.Path())
84+
continue
85+
}
86+
checked++
7787
exportdata, err := gcimporter.BExportData(conf.Fset, pkg)
7888
if err != nil {
7989
t.Fatal(err)
@@ -116,6 +126,9 @@ type UnknownType undefined
116126
}
117127
}
118128
}
129+
if want := minStdlibPackages; checked < want {
130+
t.Errorf("Checked only %d packages, want at least %d", checked, want)
131+
}
119132
}
120133

121134
func fileLine(fset *token.FileSet, obj types.Object) string {

go/internal/gcimporter/iexport_test.go

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ import (
2828
"strings"
2929
"testing"
3030

31+
"golang.org/x/tools/go/ast/inspector"
3132
"golang.org/x/tools/go/buildutil"
3233
"golang.org/x/tools/go/internal/gcimporter"
3334
"golang.org/x/tools/go/loader"
34-
"golang.org/x/tools/internal/testenv"
35+
"golang.org/x/tools/internal/typeparams/genericfeatures"
3536
)
3637

3738
func readExportFile(filename string) ([]byte, error) {
@@ -69,6 +70,8 @@ func isUnifiedBuilder() bool {
6970
return os.Getenv("GO_BUILDER_NAME") == "linux-amd64-unified"
7071
}
7172

73+
const minStdlibPackages = 248
74+
7275
func TestIExportData_stdlib(t *testing.T) {
7376
if runtime.Compiler == "gccgo" {
7477
t.Skip("gccgo standard library is inaccessible")
@@ -90,15 +93,8 @@ func TestIExportData_stdlib(t *testing.T) {
9093
Sizes: types.SizesFor(ctxt.Compiler, ctxt.GOARCH),
9194
},
9295
}
93-
// Temporarily skip packages that use generics on the unified builder, to fix
94-
// TryBots.
95-
//
96-
// TODO(#48595): fix this test with GOEXPERIMENT=unified.
97-
isUnified := isUnifiedBuilder()
9896
for _, path := range buildutil.AllPackages(conf.Build) {
99-
if !(isUnified && testenv.UsesGenerics(path)) {
100-
conf.Import(path)
101-
}
97+
conf.Import(path)
10298
}
10399

104100
// Create a package containing type and value errors to ensure
@@ -117,13 +113,19 @@ type UnknownType undefined
117113
t.Fatalf("Load failed: %v", err)
118114
}
119115

120-
numPkgs := len(prog.AllPackages)
121-
if want := 248; numPkgs < want {
122-
t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
123-
}
124-
125116
var sorted []*types.Package
117+
isUnified := isUnifiedBuilder()
126118
for pkg, info := range prog.AllPackages {
119+
// Temporarily skip packages that use generics on the unified builder, to
120+
// fix TryBots.
121+
//
122+
// TODO(#48595): fix this test with GOEXPERIMENT=unified.
123+
inspect := inspector.New(info.Files)
124+
features := genericfeatures.ForPackage(inspect, &info.Info)
125+
if isUnified && features != 0 {
126+
t.Logf("skipping package %q which uses generics", pkg.Path())
127+
continue
128+
}
127129
if info.Files != nil { // non-empty directory
128130
sorted = append(sorted, pkg)
129131
}
@@ -133,6 +135,11 @@ type UnknownType undefined
133135
})
134136

135137
version := gcimporter.IExportVersion
138+
numPkgs := len(sorted)
139+
if want := minStdlibPackages; numPkgs < want {
140+
t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
141+
}
142+
136143
for _, pkg := range sorted {
137144
if exportdata, err := iexport(conf.Fset, version, pkg); err != nil {
138145
t.Error(err)

go/ssa/stdlib_test.go

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ package ssa_test
1616

1717
import (
1818
"go/ast"
19-
"go/build"
2019
"go/token"
2120
"runtime"
2221
"testing"
2322
"time"
2423

25-
"golang.org/x/tools/go/buildutil"
26-
"golang.org/x/tools/go/loader"
24+
"golang.org/x/tools/go/ast/inspector"
25+
"golang.org/x/tools/go/packages"
2726
"golang.org/x/tools/go/ssa"
2827
"golang.org/x/tools/go/ssa/ssautil"
2928
"golang.org/x/tools/internal/testenv"
29+
"golang.org/x/tools/internal/typeparams/genericfeatures"
3030
)
3131

3232
func bytesAllocated() uint64 {
@@ -46,23 +46,27 @@ func TestStdlib(t *testing.T) {
4646
t0 := time.Now()
4747
alloc0 := bytesAllocated()
4848

49-
// Load, parse and type-check the program.
50-
ctxt := build.Default // copy
51-
ctxt.GOPATH = "" // disable GOPATH
52-
conf := loader.Config{Build: &ctxt}
53-
for _, path := range buildutil.AllPackages(conf.Build) {
54-
// Temporarily skip packages that use generics until supported by go/ssa.
55-
//
56-
// TODO(#48595): revert this once go/ssa supports generics.
57-
if !testenv.UsesGenerics(path) {
58-
conf.ImportWithTests(path)
59-
}
60-
}
61-
62-
iprog, err := conf.Load()
49+
cfg := &packages.Config{Mode: packages.LoadSyntax}
50+
pkgs, err := packages.Load(cfg, "std", "cmd")
6351
if err != nil {
64-
t.Fatalf("Load failed: %v", err)
52+
t.Fatal(err)
53+
}
54+
var nonGeneric int
55+
for i := 0; i < len(pkgs); i++ {
56+
pkg := pkgs[i]
57+
inspect := inspector.New(pkg.Syntax)
58+
features := genericfeatures.ForPackage(inspect, pkg.TypesInfo)
59+
// Skip standard library packages that use generics. This won't be
60+
// sufficient if any standard library packages start _importing_ packages
61+
// that use generics.
62+
if features != 0 {
63+
t.Logf("skipping package %q which uses generics", pkg.PkgPath)
64+
continue
65+
}
66+
pkgs[nonGeneric] = pkg
67+
nonGeneric++
6568
}
69+
pkgs = pkgs[:nonGeneric]
6670

6771
t1 := time.Now()
6872
alloc1 := bytesAllocated()
@@ -72,7 +76,7 @@ func TestStdlib(t *testing.T) {
7276
// Comment out these lines during benchmarking. Approx SSA build costs are noted.
7377
mode |= ssa.SanityCheckFunctions // + 2% space, + 4% time
7478
mode |= ssa.GlobalDebug // +30% space, +18% time
75-
prog := ssautil.CreateProgram(iprog, mode)
79+
prog, _ := ssautil.Packages(pkgs, mode)
7680

7781
t2 := time.Now()
7882

@@ -87,8 +91,8 @@ func TestStdlib(t *testing.T) {
8791
t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
8892
}
8993

90-
// Keep iprog reachable until after we've measured memory usage.
91-
if len(iprog.AllPackages) == 0 {
94+
// Keep pkgs reachable until after we've measured memory usage.
95+
if len(pkgs) == 0 {
9296
panic("unreachable")
9397
}
9498

internal/testenv/testenv.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,3 @@ func SkipAfterGo1Point(t Testing, x int) {
297297
t.Skipf("running Go version %q is version 1.%d, newer than maximum 1.%d", runtime.Version(), Go1Point(), x)
298298
}
299299
}
300-
301-
// UsesGenerics reports if the standard library package stdlibPkg uses
302-
// generics.
303-
func UsesGenerics(stdlibPkg string) bool {
304-
return stdlibPkg == "constraints"
305-
}

0 commit comments

Comments
 (0)