github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/testutils/lint/passes/unconvert/unconvert.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // Package unconvert defines an Analyzer that detects unnecessary type
    12  // conversions.
    13  package unconvert
    14  
    15  import (
    16  	"go/ast"
    17  	"go/token"
    18  	"go/types"
    19  
    20  	"golang.org/x/tools/go/analysis"
    21  	"golang.org/x/tools/go/analysis/passes/inspect"
    22  	"golang.org/x/tools/go/ast/inspector"
    23  )
    24  
    25  // Doc documents this pass.
    26  const Doc = `check for unnecessary type conversions`
    27  
    28  // Analyzer defines this pass.
    29  var Analyzer = &analysis.Analyzer{
    30  	Name:     "unconvert",
    31  	Doc:      Doc,
    32  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    33  	Run:      run,
    34  }
    35  
    36  // Adapted from https://github.com/mdempsky/unconvert/blob/beb68d938016d2dec1d1b078054f4d3db25f97be/unconvert.go#L371-L414.
    37  func run(pass *analysis.Pass) (interface{}, error) {
    38  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    39  
    40  	nodeFilter := []ast.Node{
    41  		(*ast.CallExpr)(nil),
    42  	}
    43  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    44  		call, ok := n.(*ast.CallExpr)
    45  		if !ok {
    46  			return
    47  		}
    48  		if len(call.Args) != 1 || call.Ellipsis != token.NoPos {
    49  			return
    50  		}
    51  		ft, ok := pass.TypesInfo.Types[call.Fun]
    52  		if !ok {
    53  			pass.Reportf(call.Pos(), "missing type")
    54  			return
    55  		}
    56  		if !ft.IsType() {
    57  			// Function call; not a conversion.
    58  			return
    59  		}
    60  		at, ok := pass.TypesInfo.Types[call.Args[0]]
    61  		if !ok {
    62  			pass.Reportf(call.Pos(), "missing type")
    63  			return
    64  		}
    65  		if !types.Identical(ft.Type, at.Type) {
    66  			// A real conversion.
    67  			return
    68  		}
    69  		if isUntypedValue(call.Args[0], pass.TypesInfo) {
    70  			// Workaround golang.org/issue/13061.
    71  			return
    72  		}
    73  		// Adapted from https://github.com/mdempsky/unconvert/blob/beb68d938016d2dec1d1b078054f4d3db25f97be/unconvert.go#L416-L430.
    74  		//
    75  		// cmd/cgo generates explicit type conversions that
    76  		// are often redundant when introducing
    77  		// _cgoCheckPointer calls (issue #16).  Users can't do
    78  		// anything about these, so skip over them.
    79  		if ident, ok := call.Fun.(*ast.Ident); ok {
    80  			if ident.Name == "_cgoCheckPointer" {
    81  				return
    82  			}
    83  		}
    84  		pass.Reportf(call.Pos(), "unnecessary conversion")
    85  	})
    86  
    87  	return nil, nil
    88  }
    89  
    90  // Cribbed from https://github.com/mdempsky/unconvert/blob/beb68d938016d2dec1d1b078054f4d3db25f97be/unconvert.go#L557-L607.
    91  func isUntypedValue(n ast.Expr, info *types.Info) bool {
    92  	switch n := n.(type) {
    93  	case *ast.BinaryExpr:
    94  		switch n.Op {
    95  		case token.SHL, token.SHR:
    96  			// Shifts yield an untyped value if their LHS is untyped.
    97  			return isUntypedValue(n.X, info)
    98  		case token.EQL, token.NEQ, token.LSS, token.GTR, token.LEQ, token.GEQ:
    99  			// Comparisons yield an untyped boolean value.
   100  			return true
   101  		case token.ADD, token.SUB, token.MUL, token.QUO, token.REM,
   102  			token.AND, token.OR, token.XOR, token.AND_NOT,
   103  			token.LAND, token.LOR:
   104  			return isUntypedValue(n.X, info) && isUntypedValue(n.Y, info)
   105  		}
   106  	case *ast.UnaryExpr:
   107  		switch n.Op {
   108  		case token.ADD, token.SUB, token.NOT, token.XOR:
   109  			return isUntypedValue(n.X, info)
   110  		}
   111  	case *ast.BasicLit:
   112  		// Basic literals are always untyped.
   113  		return true
   114  	case *ast.ParenExpr:
   115  		return isUntypedValue(n.X, info)
   116  	case *ast.SelectorExpr:
   117  		return isUntypedValue(n.Sel, info)
   118  	case *ast.Ident:
   119  		if obj, ok := info.Uses[n]; ok {
   120  			if obj.Pkg() == nil && obj.Name() == "nil" {
   121  				// The universal untyped zero value.
   122  				return true
   123  			}
   124  			if b, ok := obj.Type().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
   125  				// Reference to an untyped constant.
   126  				return true
   127  			}
   128  		}
   129  	case *ast.CallExpr:
   130  		if b, ok := asBuiltin(n.Fun, info); ok {
   131  			switch b.Name() {
   132  			case "real", "imag":
   133  				return isUntypedValue(n.Args[0], info)
   134  			case "complex":
   135  				return isUntypedValue(n.Args[0], info) && isUntypedValue(n.Args[1], info)
   136  			}
   137  		}
   138  	}
   139  
   140  	return false
   141  }
   142  
   143  // Cribbed from https://github.com/mdempsky/unconvert/blob/beb68d938016d2dec1d1b078054f4d3db25f97be/unconvert.go#L609-L630.
   144  func asBuiltin(n ast.Expr, info *types.Info) (*types.Builtin, bool) {
   145  	for {
   146  		paren, ok := n.(*ast.ParenExpr)
   147  		if !ok {
   148  			break
   149  		}
   150  		n = paren.X
   151  	}
   152  
   153  	ident, ok := n.(*ast.Ident)
   154  	if !ok {
   155  		return nil, false
   156  	}
   157  
   158  	obj, ok := info.Uses[ident]
   159  	if !ok {
   160  		return nil, false
   161  	}
   162  
   163  	b, ok := obj.(*types.Builtin)
   164  	return b, ok
   165  }