diff --git a/go/analysis/passes/tests/testdata/src/typeparams/typeparams.go b/go/analysis/passes/tests/testdata/src/typeparams/typeparams.go new file mode 100644 index 00000000000..344a8f86572 --- /dev/null +++ b/go/analysis/passes/tests/testdata/src/typeparams/typeparams.go @@ -0,0 +1,10 @@ +// 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 typeparams + +func Zero[T any]() T { + var zero T + return zero +} diff --git a/go/analysis/passes/tests/testdata/src/typeparams/typeparams_test.go b/go/analysis/passes/tests/testdata/src/typeparams/typeparams_test.go new file mode 100644 index 00000000000..01fad757019 --- /dev/null +++ b/go/analysis/passes/tests/testdata/src/typeparams/typeparams_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 typeparams + +import "testing" + +func Test(*testing.T) { + _ = Zero[int]() // It is fine to use generics within tests. +} + +// Note: We format {Test,Benchmark}typeParam with a 't' in "type" to avoid an error from +// cmd/go/internal/load. That package can also give an error about Test and Benchmark +// functions with TypeParameters. These tests may need to be updated if that logic changes. +func TesttypeParam[T any](*testing.T) {} // want "TesttypeParam has type parameters: it will not be run by go test as a TestXXX function" "TesttypeParam has malformed name" +func BenchmarktypeParam[T any](*testing.B) {} // want "BenchmarktypeParam has type parameters: it will not be run by go test as a BenchmarkXXX function" "BenchmarktypeParam has malformed name" + +func ExampleZero[T any]() { // want "ExampleZero should not have type params" + print(Zero[T]()) +} diff --git a/go/analysis/passes/tests/tests.go b/go/analysis/passes/tests/tests.go index 570ad5c2096..2c878824966 100644 --- a/go/analysis/passes/tests/tests.go +++ b/go/analysis/passes/tests/tests.go @@ -16,6 +16,7 @@ import ( "unicode/utf8" "golang.org/x/tools/go/analysis" + "golang.org/x/tools/internal/typeparams" ) const Doc = `check for common mistaken usages of tests and examples @@ -170,6 +171,9 @@ func checkExampleName(pass *analysis.Pass, fn *ast.FuncDecl) { if results := fn.Type.Results; results != nil && len(results.List) != 0 { pass.Reportf(fn.Pos(), "%s should return nothing", fnName) } + if tparams := typeparams.ForFuncType(fn.Type); tparams != nil && len(tparams.List) > 0 { + pass.Reportf(fn.Pos(), "%s should not have type params", fnName) + } if fnName == "Example" { // Nothing more to do. @@ -236,6 +240,12 @@ func checkTest(pass *analysis.Pass, fn *ast.FuncDecl, prefix string) { return } + if tparams := typeparams.ForFuncType(fn.Type); tparams != nil && len(tparams.List) > 0 { + // Note: cmd/go/internal/load also errors about TestXXX and BenchmarkXXX functions with type parameters. + // We have currently decided to also warn before compilation/package loading. This can help users in IDEs. + pass.Reportf(fn.Pos(), "%s has type parameters: it will not be run by go test as a %sXXX function", fn.Name.Name, prefix) + } + if !isTestSuffix(fn.Name.Name[len(prefix):]) { pass.Reportf(fn.Pos(), "%s has malformed name: first letter after '%s' must not be lowercase", fn.Name.Name, prefix) } diff --git a/go/analysis/passes/tests/tests_test.go b/go/analysis/passes/tests/tests_test.go index 34efbf6e096..740adc5d5e4 100644 --- a/go/analysis/passes/tests/tests_test.go +++ b/go/analysis/passes/tests/tests_test.go @@ -9,14 +9,18 @@ import ( "golang.org/x/tools/go/analysis/analysistest" "golang.org/x/tools/go/analysis/passes/tests" + "golang.org/x/tools/internal/typeparams" ) func Test(t *testing.T) { testdata := analysistest.TestData() - - analysistest.Run(t, testdata, tests.Analyzer, + pkgs := []string{ "a", // loads "a", "a [a.test]", and "a.test" "b_x_test", // loads "b" and "b_x_test" "divergent", - ) + } + if typeparams.Enabled { + pkgs = append(pkgs, "typeparams") + } + analysistest.Run(t, testdata, tests.Analyzer, pkgs...) }