diff --git a/go/analysis/passes/shift/shift.go b/go/analysis/passes/shift/shift.go index 1f3df07ccd..640de28e05 100644 --- a/go/analysis/passes/shift/shift.go +++ b/go/analysis/passes/shift/shift.go @@ -14,11 +14,13 @@ import ( "go/ast" "go/constant" "go/token" + "math" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/internal/typeparams" ) const Doc = "check for shifts that equal or exceed the width of the integer" @@ -93,9 +95,27 @@ func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) { if t == nil { return } - size := 8 * pass.TypesSizes.Sizeof(t) - if amt >= size { + terms, err := typeparams.StructuralTerms(t) + if err != nil { + return // invalid type + } + sizes := make(map[int64]struct{}) + for _, term := range terms { + size := 8 * pass.TypesSizes.Sizeof(term.Type()) + sizes[size] = struct{}{} + } + minSize := int64(math.MaxInt64) + for size := range sizes { + if size < minSize { + minSize = size + } + } + if amt >= minSize { ident := analysisutil.Format(pass.Fset, x) - pass.ReportRangef(node, "%s (%d bits) too small for shift of %d", ident, size, amt) + qualifier := "" + if len(sizes) > 1 { + qualifier = "may be " + } + pass.ReportRangef(node, "%s (%s%d bits) too small for shift of %d", ident, qualifier, minSize, amt) } } diff --git a/go/analysis/passes/shift/shift_test.go b/go/analysis/passes/shift/shift_test.go index 8b41b609b6..e60943e37c 100644 --- a/go/analysis/passes/shift/shift_test.go +++ b/go/analysis/passes/shift/shift_test.go @@ -9,9 +9,14 @@ import ( "golang.org/x/tools/go/analysis/analysistest" "golang.org/x/tools/go/analysis/passes/shift" + "golang.org/x/tools/internal/typeparams" ) func Test(t *testing.T) { testdata := analysistest.TestData() - analysistest.Run(t, testdata, shift.Analyzer, "a") + pkgs := []string{"a"} + if typeparams.Enabled { + pkgs = append(pkgs, "typeparams") + } + analysistest.Run(t, testdata, shift.Analyzer, pkgs...) } diff --git a/go/analysis/passes/shift/testdata/src/typeparams/typeparams.go b/go/analysis/passes/shift/testdata/src/typeparams/typeparams.go new file mode 100644 index 0000000000..a76df880f1 --- /dev/null +++ b/go/analysis/passes/shift/testdata/src/typeparams/typeparams.go @@ -0,0 +1,32 @@ +// 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 "unsafe" + +func GenericShiftTest[DifferentSize ~int8|int16|int64, SameSize int8|byte]() { + var d DifferentSize + _ = d << 7 + _ = d << 8 // want "d .may be 8 bits. too small for shift of 8" + _ = d << 15 // want "d .may be 8 bits. too small for shift of 15" + _ = (d + 1) << 8 // want ".d . 1. .may be 8 bits. too small for shift of 8" + _ = (d + 1) << 16 // want ".d . 1. .may be 8 bits. too small for shift of 16" + _ = d << (7 + 1) // want "d .may be 8 bits. too small for shift of 8" + _ = d >> 8 // want "d .may be 8 bits. too small for shift of 8" + d <<= 8 // want "d .may be 8 bits. too small for shift of 8" + d >>= 8 // want "d .may be 8 bits. too small for shift of 8" + + // go/types does not compute constant sizes for type parameters, so we do not + // report a diagnostic here. + _ = d << (8 * DifferentSize(unsafe.Sizeof(d))) + + var s SameSize + _ = s << 7 + _ = s << 8 // want "s .8 bits. too small for shift of 8" + _ = s << (7 + 1) // want "s .8 bits. too small for shift of 8" + _ = s >> 8 // want "s .8 bits. too small for shift of 8" + s <<= 8 // want "s .8 bits. too small for shift of 8" + s >>= 8 // want "s .8 bits. too small for shift of 8" +}