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 0000000000..a6dd88819c --- /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 0000000000..81c2810f00 --- /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 0000000000..f07499e84d --- /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 0000000000..a06c77651c --- /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 0000000000..73b53fbefa --- /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 0000000000..3dcff240d4 --- /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 96654c0011..1b767e76d0 100644 --- a/internal/lsp/analysis/infertypeargs/run_go118.go +++ b/internal/lsp/analysis/infertypeargs/run_go118.go @@ -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. diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go index 140a9d2cb0..f8fddac1ec 100644 --- a/internal/lsp/source/identifier.go +++ b/internal/lsp/source/identifier.go @@ -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 } diff --git a/internal/typeparams/typeparams_go117.go b/internal/typeparams/typeparams_go117.go index d22899d29e..5a536526c5 100644 --- a/internal/typeparams/typeparams_go117.go +++ b/internal/typeparams/typeparams_go117.go @@ -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. diff --git a/internal/typeparams/typeparams_go118.go b/internal/typeparams/typeparams_go118.go index a252183411..e3c48221dd 100644 --- a/internal/typeparams/typeparams_go118.go +++ b/internal/typeparams/typeparams_go118.go @@ -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.