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 <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Tim King <taking@google.com>
diff --git a/go/analysis/passes/usesgenerics/testdata/src/a/a.go b/go/analysis/passes/usesgenerics/testdata/src/a/a.go
new file mode 100644
index 0000000..a6dd888
--- /dev/null
+++ b/go/analysis/passes/usesgenerics/testdata/src/a/a.go
@@ -0,0 +1,9 @@
+// want package:`features{typeDecl,funcDecl,funcInstance}`
+
+package a
+
+type T[P any] int
+
+func F[P any]() {}
+
+var _ = F[int]
diff --git a/go/analysis/passes/usesgenerics/testdata/src/b/b.go b/go/analysis/passes/usesgenerics/testdata/src/b/b.go
new file mode 100644
index 0000000..81c2810
--- /dev/null
+++ b/go/analysis/passes/usesgenerics/testdata/src/b/b.go
@@ -0,0 +1,7 @@
+// want package:`features{typeSet}`
+
+package b
+
+type Constraint interface {
+	~int | string
+}
diff --git a/go/analysis/passes/usesgenerics/testdata/src/c/c.go b/go/analysis/passes/usesgenerics/testdata/src/c/c.go
new file mode 100644
index 0000000..f07499e
--- /dev/null
+++ b/go/analysis/passes/usesgenerics/testdata/src/c/c.go
@@ -0,0 +1,13 @@
+// want package:`features{typeDecl,funcDecl,typeSet,typeInstance,funcInstance}`
+
+// Features funcDecl, typeSet, and funcInstance come from imported packages "a"
+// and "b". These features are not directly present in "c".
+
+package c
+
+import (
+	"a"
+	"b"
+)
+
+type T[P b.Constraint] a.T[P]
diff --git a/go/analysis/passes/usesgenerics/testdata/src/d/d.go b/go/analysis/passes/usesgenerics/testdata/src/d/d.go
new file mode 100644
index 0000000..a06c776
--- /dev/null
+++ b/go/analysis/passes/usesgenerics/testdata/src/d/d.go
@@ -0,0 +1,13 @@
+// want package:`features{typeSet}`
+
+package d
+
+type myInt int
+
+func _() {
+	// Sanity check that we can both detect local types and interfaces with
+	// embedded defined types.
+	type constraint interface {
+		myInt
+	}
+}
diff --git a/go/analysis/passes/usesgenerics/usesgenerics.go b/go/analysis/passes/usesgenerics/usesgenerics.go
new file mode 100644
index 0000000..73b53fb
--- /dev/null
+++ b/go/analysis/passes/usesgenerics/usesgenerics.go
@@ -0,0 +1,166 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package usesgenerics defines an Analyzer that checks for usage of generic
+// features added in Go 1.18.
+package usesgenerics
+
+import (
+	"go/ast"
+	"go/types"
+	"reflect"
+	"strings"
+
+	"golang.org/x/tools/go/analysis"
+	"golang.org/x/tools/go/analysis/passes/inspect"
+	"golang.org/x/tools/go/ast/inspector"
+	"golang.org/x/tools/internal/typeparams"
+)
+
+var Analyzer = &analysis.Analyzer{
+	Name:       "usesgenerics",
+	Doc:        Doc,
+	Requires:   []*analysis.Analyzer{inspect.Analyzer},
+	Run:        run,
+	ResultType: reflect.TypeOf((*Result)(nil)),
+	FactTypes:  []analysis.Fact{new(featuresFact)},
+}
+
+const Doc = `detect whether a package uses generics features
+
+The usesgenerics analysis reports whether a package directly or transitively
+uses certain features associated with generic programming in Go.`
+
+// Result is the usesgenerics analyzer result type. The Direct field records
+// features used directly by the package being analyzed (i.e. contained in the
+// package source code). The Transitive field records any features used by the
+// package or any of its transitive imports.
+type Result struct {
+	Direct, Transitive Features
+}
+
+// Features is a set of flags reporting which features of generic Go code a
+// package uses, or 0.
+type Features int
+
+const (
+	// GenericTypeDecls indicates whether the package declares types with type
+	// parameters.
+	GenericTypeDecls Features = 1 << iota
+
+	// GenericFuncDecls indicates whether the package declares functions with
+	// type parameters.
+	GenericFuncDecls
+
+	// EmbeddedTypeSets indicates whether the package declares interfaces that
+	// contain structural type restrictions, i.e. are not fully described by
+	// their method sets.
+	EmbeddedTypeSets
+
+	// TypeInstantiation indicates whether the package instantiates any generic
+	// types.
+	TypeInstantiation
+
+	// FuncInstantiation indicates whether the package instantiates any generic
+	// functions.
+	FuncInstantiation
+)
+
+func (f Features) String() string {
+	var feats []string
+	if f&GenericTypeDecls != 0 {
+		feats = append(feats, "typeDecl")
+	}
+	if f&GenericFuncDecls != 0 {
+		feats = append(feats, "funcDecl")
+	}
+	if f&EmbeddedTypeSets != 0 {
+		feats = append(feats, "typeSet")
+	}
+	if f&TypeInstantiation != 0 {
+		feats = append(feats, "typeInstance")
+	}
+	if f&FuncInstantiation != 0 {
+		feats = append(feats, "funcInstance")
+	}
+	return "features{" + strings.Join(feats, ",") + "}"
+}
+
+type featuresFact struct {
+	Features Features
+}
+
+func (f *featuresFact) AFact()         {}
+func (f *featuresFact) String() string { return f.Features.String() }
+
+func run(pass *analysis.Pass) (interface{}, error) {
+	direct := directFeatures(pass)
+
+	transitive := direct | importedTransitiveFeatures(pass)
+	if transitive != 0 {
+		pass.ExportPackageFact(&featuresFact{transitive})
+	}
+
+	return &Result{
+		Direct:     direct,
+		Transitive: transitive,
+	}, nil
+}
+
+// directFeatures computes which generic features are used directly by the
+// package being analyzed.
+func directFeatures(pass *analysis.Pass) Features {
+	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+
+	nodeFilter := []ast.Node{
+		(*ast.FuncType)(nil),
+		(*ast.InterfaceType)(nil),
+		(*ast.ImportSpec)(nil),
+		(*ast.TypeSpec)(nil),
+	}
+
+	var direct Features
+
+	inspect.Preorder(nodeFilter, func(node ast.Node) {
+		switch n := node.(type) {
+		case *ast.FuncType:
+			if tparams := typeparams.ForFuncType(n); tparams != nil {
+				direct |= GenericFuncDecls
+			}
+		case *ast.InterfaceType:
+			tv := pass.TypesInfo.Types[n]
+			if iface, _ := tv.Type.(*types.Interface); iface != nil && !typeparams.IsMethodSet(iface) {
+				direct |= EmbeddedTypeSets
+			}
+		case *ast.TypeSpec:
+			if tparams := typeparams.ForTypeSpec(n); tparams != nil {
+				direct |= GenericTypeDecls
+			}
+		}
+	})
+
+	instances := typeparams.GetInstances(pass.TypesInfo)
+	for _, inst := range instances {
+		switch inst.Type.(type) {
+		case *types.Named:
+			direct |= TypeInstantiation
+		case *types.Signature:
+			direct |= FuncInstantiation
+		}
+	}
+	return direct
+}
+
+// importedTransitiveFeatures computes features that are used transitively via
+// imports.
+func importedTransitiveFeatures(pass *analysis.Pass) Features {
+	var feats Features
+	for _, imp := range pass.Pkg.Imports() {
+		var importedFact featuresFact
+		if pass.ImportPackageFact(imp, &importedFact) {
+			feats |= importedFact.Features
+		}
+	}
+	return feats
+}
diff --git a/go/analysis/passes/usesgenerics/usesgenerics_test.go b/go/analysis/passes/usesgenerics/usesgenerics_test.go
new file mode 100644
index 0000000..3dcff24
--- /dev/null
+++ b/go/analysis/passes/usesgenerics/usesgenerics_test.go
@@ -0,0 +1,21 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package usesgenerics_test
+
+import (
+	"testing"
+
+	"golang.org/x/tools/go/analysis/analysistest"
+	"golang.org/x/tools/go/analysis/passes/usesgenerics"
+	"golang.org/x/tools/internal/typeparams"
+)
+
+func Test(t *testing.T) {
+	if !typeparams.Enabled {
+		t.Skip("type parameters are not enabled at this Go version")
+	}
+	testdata := analysistest.TestData()
+	analysistest.Run(t, testdata, usesgenerics.Analyzer, "a", "b", "c", "d")
+}
diff --git a/internal/lsp/analysis/infertypeargs/run_go118.go b/internal/lsp/analysis/infertypeargs/run_go118.go
index 96654c0..1b767e7 100644
--- a/internal/lsp/analysis/infertypeargs/run_go118.go
+++ b/internal/lsp/analysis/infertypeargs/run_go118.go
@@ -33,10 +33,11 @@
 		}
 
 		// Confirm that instantiation actually occurred at this ident.
-		_, instance := typeparams.GetInstance(pass.TypesInfo, ident)
-		if instance == nil {
+		idata, ok := typeparams.GetInstances(pass.TypesInfo)[ident]
+		if !ok {
 			return // something went wrong, but fail open
 		}
+		instance := idata.Type
 
 		// Start removing argument expressions from the right, and check if we can
 		// still infer the call expression.
@@ -62,7 +63,8 @@
 				// Most likely inference failed.
 				break
 			}
-			_, newInstance := typeparams.GetInstance(info, ident)
+			newIData := typeparams.GetInstances(info)[ident]
+			newInstance := newIData.Type
 			if !types.Identical(instance, newInstance) {
 				// The inferred result type does not match the original result type, so
 				// this simplification is not valid.
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index 140a9d2..f8fddac 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -354,8 +354,8 @@
 //
 // If no such signature exists, it returns nil.
 func inferredSignature(info *types.Info, id *ast.Ident) *types.Signature {
-	_, typ := typeparams.GetInstance(info, id)
-	sig, _ := typ.(*types.Signature)
+	inst := typeparams.GetInstances(info)[id]
+	sig, _ := inst.Type.(*types.Signature)
 	return sig
 }
 
diff --git a/internal/typeparams/typeparams_go117.go b/internal/typeparams/typeparams_go117.go
index d22899d..5a53652 100644
--- a/internal/typeparams/typeparams_go117.go
+++ b/internal/typeparams/typeparams_go117.go
@@ -186,9 +186,16 @@
 // InitInstanceInfo is a noop at this Go version.
 func InitInstanceInfo(*types.Info) {}
 
-// GetInstance returns nothing, as type parameters are not supported at this Go
-// version.
-func GetInstance(*types.Info, *ast.Ident) (*TypeList, types.Type) { return nil, nil }
+// Instance is a placeholder type, as type parameters are not supported at this
+// Go version.
+type Instance struct {
+	TypeArgs *TypeList
+	Type     types.Type
+}
+
+// GetInstances returns a nil map, as type parameters are not supported at this
+// Go version.
+func GetInstances(info *types.Info) map[*ast.Ident]Instance { return nil }
 
 // Context is a placeholder type, as type parameters are not supported at
 // this Go version.
diff --git a/internal/typeparams/typeparams_go118.go b/internal/typeparams/typeparams_go118.go
index a252183..e3c4822 100644
--- a/internal/typeparams/typeparams_go118.go
+++ b/internal/typeparams/typeparams_go118.go
@@ -169,15 +169,12 @@
 	info.Instances = make(map[*ast.Ident]types.Instance)
 }
 
-// GetInstance extracts information about the instantiation occurring at the
-// identifier id. id should be the identifier denoting a parameterized type or
-// function in an instantiation expression or function call.
-func GetInstance(info *types.Info, id *ast.Ident) (*TypeList, types.Type) {
-	if info.Instances != nil {
-		inf := info.Instances[id]
-		return inf.TypeArgs, inf.Type
-	}
-	return nil, nil
+// Instance is an alias for types.Instance.
+type Instance = types.Instance
+
+// GetInstances returns info.Instances.
+func GetInstances(info *types.Info) map[*ast.Ident]Instance {
+	return info.Instances
 }
 
 // Context is an alias for types.Context.