Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesdelete.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package modernize
     6  
     7  import (
     8  	"go/ast"
     9  	"go/constant"
    10  	"go/token"
    11  	"go/types"
    12  
    13  	"golang.org/x/tools/go/analysis"
    14  	"golang.org/x/tools/go/analysis/passes/inspect"
    15  	"golang.org/x/tools/internal/analysis/analyzerutil"
    16  	"golang.org/x/tools/internal/astutil"
    17  	"golang.org/x/tools/internal/refactor"
    18  	"golang.org/x/tools/internal/typesinternal"
    19  	"golang.org/x/tools/internal/versions"
    20  )
    21  
    22  // Warning: this analyzer is not safe to enable by default (not nil-preserving).
    23  var SlicesDeleteAnalyzer = &analysis.Analyzer{
    24  	Name:     "slicesdelete",
    25  	Doc:      analyzerutil.MustExtractDoc(doc, "slicesdelete"),
    26  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    27  	Run:      slicesdelete,
    28  	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicesdelete",
    29  }
    30  
    31  // The slicesdelete pass attempts to replace instances of append(s[:i], s[i+k:]...)
    32  // with slices.Delete(s, i, i+k) where k is some positive constant.
    33  // Other variations that will also have suggested replacements include:
    34  // append(s[:i-1], s[i:]...) and append(s[:i+k1], s[i+k2:]) where k2 > k1.
    35  func slicesdelete(pass *analysis.Pass) (any, error) {
    36  	// Skip the analyzer in packages where its
    37  	// fixes would create an import cycle.
    38  	if within(pass, "slices", "runtime") {
    39  		return nil, nil
    40  	}
    41  
    42  	info := pass.TypesInfo
    43  	report := func(file *ast.File, call *ast.CallExpr, slice1, slice2 *ast.SliceExpr) {
    44  		insert := func(pos token.Pos, text string) analysis.TextEdit {
    45  			return analysis.TextEdit{Pos: pos, End: pos, NewText: []byte(text)}
    46  		}
    47  		isIntExpr := func(e ast.Expr) bool {
    48  			return types.Identical(types.Default(info.TypeOf(e)), builtinInt.Type())
    49  		}
    50  		isIntShadowed := func() bool {
    51  			scope := info.Scopes[file].Innermost(call.Lparen)
    52  			if _, obj := scope.LookupParent("int", call.Lparen); obj != builtinInt {
    53  				return true // int type is shadowed
    54  			}
    55  			return false
    56  		}
    57  
    58  		prefix, edits := refactor.AddImport(info, file, "slices", "slices", "Delete", call.Pos())
    59  		// append's indices may be any integer type; slices.Delete requires int.
    60  		// Insert int conversions as needed (and if possible).
    61  		if isIntShadowed() && (!isIntExpr(slice1.High) || !isIntExpr(slice2.Low)) {
    62  			return
    63  		}
    64  		if !isIntExpr(slice1.High) {
    65  			edits = append(edits,
    66  				insert(slice1.High.Pos(), "int("),
    67  				insert(slice1.High.End(), ")"),
    68  			)
    69  		}
    70  		if !isIntExpr(slice2.Low) {
    71  			edits = append(edits,
    72  				insert(slice2.Low.Pos(), "int("),
    73  				insert(slice2.Low.End(), ")"),
    74  			)
    75  		}
    76  
    77  		pass.Report(analysis.Diagnostic{
    78  			Pos:     call.Pos(),
    79  			End:     call.End(),
    80  			Message: "Replace append with slices.Delete",
    81  			SuggestedFixes: []analysis.SuggestedFix{{
    82  				Message: "Replace append with slices.Delete",
    83  				TextEdits: append(edits, []analysis.TextEdit{
    84  					// Change name of called function.
    85  					{
    86  						Pos:     call.Fun.Pos(),
    87  						End:     call.Fun.End(),
    88  						NewText: []byte(prefix + "Delete"),
    89  					},
    90  					// Delete ellipsis.
    91  					{
    92  						Pos: call.Ellipsis,
    93  						End: call.Ellipsis + token.Pos(len("...")), // delete ellipsis
    94  					},
    95  					// Remove second slice variable name.
    96  					{
    97  						Pos: slice2.X.Pos(),
    98  						End: slice2.X.End(),
    99  					},
   100  					// Insert after first slice variable name.
   101  					{
   102  						Pos:     slice1.X.End(),
   103  						NewText: []byte(", "),
   104  					},
   105  					// Remove brackets and colons.
   106  					{
   107  						Pos: slice1.Lbrack,
   108  						End: slice1.High.Pos(),
   109  					},
   110  					{
   111  						Pos: slice1.Rbrack,
   112  						End: slice1.Rbrack + 1,
   113  					},
   114  					{
   115  						Pos: slice2.Lbrack,
   116  						End: slice2.Lbrack + 1,
   117  					},
   118  					{
   119  						Pos: slice2.Low.End(),
   120  						End: slice2.Rbrack + 1,
   121  					},
   122  				}...),
   123  			}},
   124  		})
   125  	}
   126  	for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
   127  		file := curFile.Node().(*ast.File)
   128  		for curCall := range curFile.Preorder((*ast.CallExpr)(nil)) {
   129  			call := curCall.Node().(*ast.CallExpr)
   130  			if id, ok := call.Fun.(*ast.Ident); ok && len(call.Args) == 2 {
   131  				// Verify we have append with two slices and ... operator,
   132  				// the first slice has no low index and second slice has no
   133  				// high index, and not a three-index slice.
   134  				if call.Ellipsis.IsValid() && info.Uses[id] == builtinAppend {
   135  					slice1, ok1 := call.Args[0].(*ast.SliceExpr)
   136  					slice2, ok2 := call.Args[1].(*ast.SliceExpr)
   137  					if ok1 && slice1.Low == nil && !slice1.Slice3 &&
   138  						ok2 && slice2.High == nil && !slice2.Slice3 &&
   139  						astutil.EqualSyntax(slice1.X, slice2.X) &&
   140  						typesinternal.NoEffects(info, slice1.X) &&
   141  						increasingSliceIndices(info, slice1.High, slice2.Low) {
   142  						// Have append(s[:a], s[b:]...) where we can verify a < b.
   143  						report(file, call, slice1, slice2)
   144  					}
   145  				}
   146  			}
   147  		}
   148  	}
   149  	return nil, nil
   150  }
   151  
   152  // Given two slice indices a and b, returns true if we can verify that a < b.
   153  // It recognizes certain forms such as i+k1 < i+k2 where k1 < k2.
   154  func increasingSliceIndices(info *types.Info, a, b ast.Expr) bool {
   155  	// Given an expression of the form i±k, returns (i, k)
   156  	// where k is a signed constant. Otherwise it returns (e, 0).
   157  	split := func(e ast.Expr) (ast.Expr, constant.Value) {
   158  		if binary, ok := e.(*ast.BinaryExpr); ok && (binary.Op == token.SUB || binary.Op == token.ADD) {
   159  			// Negate constants if operation is subtract instead of add
   160  			if k := info.Types[binary.Y].Value; k != nil {
   161  				return binary.X, constant.UnaryOp(binary.Op, k, 0) // i ± k
   162  			}
   163  		}
   164  		return e, constant.MakeInt64(0)
   165  	}
   166  
   167  	// Handle case where either a or b is a constant
   168  	ak := info.Types[a].Value
   169  	bk := info.Types[b].Value
   170  	if ak != nil || bk != nil {
   171  		return ak != nil && bk != nil && constant.Compare(ak, token.LSS, bk)
   172  	}
   173  
   174  	ai, ak := split(a)
   175  	bi, bk := split(b)
   176  	return astutil.EqualSyntax(ai, bi) && constant.Compare(ak, token.LSS, bk)
   177  }
   178  

View as plain text