mirror of https://github.com/golang/go.git
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>
This commit is contained in:
parent
94178a22b2
commit
074820e17b
|
|
@ -0,0 +1,9 @@
|
|||
// want package:`features{typeDecl,funcDecl,funcInstance}`
|
||||
|
||||
package a
|
||||
|
||||
type T[P any] int
|
||||
|
||||
func F[P any]() {}
|
||||
|
||||
var _ = F[int]
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// want package:`features{typeSet}`
|
||||
|
||||
package b
|
||||
|
||||
type Constraint interface {
|
||||
~int | string
|
||||
}
|
||||
|
|
@ -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]
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -33,10 +33,11 @@ func run(pass *analysis.Pass) (interface{}, error) {
|
|||
}
|
||||
|
||||
// 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 @@ func run(pass *analysis.Pass) (interface{}, error) {
|
|||
// 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.
|
||||
|
|
|
|||
|
|
@ -354,8 +354,8 @@ func fullNode(snapshot Snapshot, obj types.Object, pkg Package) (ast.Decl, error
|
|||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -186,9 +186,16 @@ func NewUnion(terms []*Term) *Union {
|
|||
// 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.
|
||||
|
|
|
|||
|
|
@ -169,15 +169,12 @@ func InitInstanceInfo(info *types.Info) {
|
|||
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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue