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

     1  // Copyright 2025 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  	"fmt"
     9  	"go/ast"
    10  	"go/constant"
    11  	"go/token"
    12  	"go/types"
    13  	"iter"
    14  	"strconv"
    15  
    16  	"golang.org/x/tools/go/analysis"
    17  	"golang.org/x/tools/go/analysis/passes/inspect"
    18  	"golang.org/x/tools/go/ast/edge"
    19  	"golang.org/x/tools/go/ast/inspector"
    20  	"golang.org/x/tools/go/types/typeutil"
    21  	"golang.org/x/tools/internal/analysis/analyzerutil"
    22  	typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
    23  	"golang.org/x/tools/internal/astutil"
    24  	"golang.org/x/tools/internal/goplsexport"
    25  	"golang.org/x/tools/internal/refactor"
    26  	"golang.org/x/tools/internal/typesinternal"
    27  	"golang.org/x/tools/internal/typesinternal/typeindex"
    28  	"golang.org/x/tools/internal/versions"
    29  )
    30  
    31  var stringscutAnalyzer = &analysis.Analyzer{
    32  	Name: "stringscut",
    33  	Doc:  analyzerutil.MustExtractDoc(doc, "stringscut"),
    34  	Requires: []*analysis.Analyzer{
    35  		inspect.Analyzer,
    36  		typeindexanalyzer.Analyzer,
    37  	},
    38  	Run: stringscut,
    39  	URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize#stringscut",
    40  }
    41  
    42  func init() {
    43  	// Export to gopls until this is a published modernizer.
    44  	goplsexport.StringsCutModernizer = stringscutAnalyzer
    45  }
    46  
    47  // stringscut offers a fix to replace an occurrence of strings.Index{,Byte} with
    48  // strings.{Cut,Contains}, and similar fixes for functions in the bytes package.
    49  // Consider some candidate for replacement i := strings.Index(s, substr).
    50  // The following must hold for a replacement to occur:
    51  //
    52  //  1. All instances of i and s must be in one of these forms.
    53  //
    54  //     Binary expressions must be inequalities equivalent to
    55  //     "Index failed" (e.g. i < 0) or "Index succeeded" (i >= 0),
    56  //     or identities such as these (and their negations):
    57  //
    58  //     0 > i                 (flips left and right)
    59  //     i <= -1, -1 >= i      (replace strict inequality by non-strict)
    60  //     i == -1, -1 == i      (Index() guarantees i < 0 => i == -1)
    61  //
    62  //     Slice expressions:
    63  //     a: s[:i], s[0:i]
    64  //     b: s[i+len(substr):], s[len(substr) + i:], s[i + const], s[k + i] (where k = len(substr))
    65  //
    66  //  2. There can be no uses of s, substr, or i where they are
    67  //     potentially modified (i.e. in assignments, or function calls with unknown side
    68  //     effects).
    69  //
    70  // Then, the replacement involves the following substitutions:
    71  //
    72  //  1. Replace "i := strings.Index(s, substr)" with "before, after, ok := strings.Cut(s, substr)"
    73  //
    74  //  2. Replace instances of binary expressions (a) with !ok and binary expressions (b) with ok.
    75  //
    76  //  3. Replace slice expressions (a) with "before" and slice expressions (b) with after.
    77  //
    78  //  4. The assignments to before, after, and ok may use the blank identifier "_" if they are unused.
    79  //
    80  //     For example:
    81  //
    82  //     i := strings.Index(s, substr)
    83  //     if i >= 0 {
    84  //     use(s[:i], s[i+len(substr):])
    85  //     }
    86  //
    87  //     Would become:
    88  //
    89  //     before, after, ok := strings.Cut(s, substr)
    90  //     if ok {
    91  //     use(before, after)
    92  //     }
    93  //
    94  // If the condition involving `i` is equivalent to i >= 0, then we replace it with
    95  // `if ok“.
    96  // If the condition is negated (e.g. equivalent to `i < 0`), we use `if !ok` instead.
    97  // If the slices of `s` match `s[:i]` or `s[i+len(substr):]` or their variants listed above,
    98  // then we replace them with before and after.
    99  //
   100  // When the index `i` is used only to check for the presence of the substring or byte slice,
   101  // the suggested fix uses Contains() instead of Cut.
   102  //
   103  // For example:
   104  //
   105  //	i := strings.Index(s, substr)
   106  //	if i >= 0 {
   107  //		return
   108  //	}
   109  //
   110  // Would become:
   111  //
   112  //	found := strings.Contains(s, substr)
   113  //	if found {
   114  //		return
   115  //	}
   116  func stringscut(pass *analysis.Pass) (any, error) {
   117  	var (
   118  		index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
   119  		info  = pass.TypesInfo
   120  
   121  		stringsIndex     = index.Object("strings", "Index")
   122  		stringsIndexByte = index.Object("strings", "IndexByte")
   123  		bytesIndex       = index.Object("bytes", "Index")
   124  		bytesIndexByte   = index.Object("bytes", "IndexByte")
   125  	)
   126  
   127  	for _, obj := range []types.Object{
   128  		stringsIndex,
   129  		stringsIndexByte,
   130  		bytesIndex,
   131  		bytesIndexByte,
   132  	} {
   133  		// (obj may be nil)
   134  	nextcall:
   135  		for curCall := range index.Calls(obj) {
   136  			// Check file version.
   137  			if !analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curCall), versions.Go1_18) {
   138  				continue // strings.Index not available in this file
   139  			}
   140  			indexCall := curCall.Node().(*ast.CallExpr) // the call to strings.Index, etc.
   141  			obj := typeutil.Callee(info, indexCall)
   142  			if obj == nil {
   143  				continue
   144  			}
   145  
   146  			var iIdent *ast.Ident // defining identifier of i var
   147  			switch ek, idx := curCall.ParentEdge(); ek {
   148  			case edge.ValueSpec_Values:
   149  				// Have: var i = strings.Index(...)
   150  				curName := curCall.Parent().ChildAt(edge.ValueSpec_Names, idx)
   151  				iIdent = curName.Node().(*ast.Ident)
   152  			case edge.AssignStmt_Rhs:
   153  				// Have: i := strings.Index(...)
   154  				// (Must be i's definition.)
   155  				curLhs := curCall.Parent().ChildAt(edge.AssignStmt_Lhs, idx)
   156  				iIdent, _ = curLhs.Node().(*ast.Ident) // may be nil
   157  			}
   158  
   159  			if iIdent == nil {
   160  				continue
   161  			}
   162  			// Inv: iIdent is i's definition. The following would be skipped: 'var i int; i = strings.Index(...)'
   163  			// Get uses of i.
   164  			iObj := info.ObjectOf(iIdent)
   165  			if iObj == nil {
   166  				continue
   167  			}
   168  
   169  			var (
   170  				s      = indexCall.Args[0]
   171  				substr = indexCall.Args[1]
   172  			)
   173  
   174  			// Check that there are no statements that alter the value of s
   175  			// or substr after the call to Index().
   176  			if !indexArgValid(info, index, s, indexCall.Pos()) ||
   177  				!indexArgValid(info, index, substr, indexCall.Pos()) {
   178  				continue nextcall
   179  			}
   180  
   181  			// Next, examine all uses of i. If the only uses are of the
   182  			// forms mentioned above (e.g. i < 0, i >= 0, s[:i] and s[i +
   183  			// len(substr)]), then we can replace the call to Index()
   184  			// with a call to Cut() and use the returned ok, before,
   185  			// and after variables accordingly.
   186  			negative, nonnegative, beforeSlice, afterSlice := checkIdxUses(pass.TypesInfo, index.Uses(iObj), s, substr)
   187  
   188  			// Either there are no uses of before, after, or ok, or some use
   189  			// of i does not match our criteria - don't suggest a fix.
   190  			if negative == nil && nonnegative == nil && beforeSlice == nil && afterSlice == nil {
   191  				continue
   192  			}
   193  
   194  			// If the only uses are ok and !ok, don't suggest a Cut() fix - these should be using Contains()
   195  			isContains := (len(negative) > 0 || len(nonnegative) > 0) && len(beforeSlice) == 0 && len(afterSlice) == 0
   196  
   197  			scope := iObj.Parent()
   198  			var (
   199  				// TODO(adonovan): avoid FreshName when not needed; see errorsastype.
   200  				okVarName     = refactor.FreshName(scope, iIdent.Pos(), "ok")
   201  				beforeVarName = refactor.FreshName(scope, iIdent.Pos(), "before")
   202  				afterVarName  = refactor.FreshName(scope, iIdent.Pos(), "after")
   203  				foundVarName  = refactor.FreshName(scope, iIdent.Pos(), "found") // for Contains()
   204  			)
   205  
   206  			// If there will be no uses of ok, before, or after, use the
   207  			// blank identifier instead.
   208  			if len(negative) == 0 && len(nonnegative) == 0 {
   209  				okVarName = "_"
   210  			}
   211  			if len(beforeSlice) == 0 {
   212  				beforeVarName = "_"
   213  			}
   214  			if len(afterSlice) == 0 {
   215  				afterVarName = "_"
   216  			}
   217  
   218  			var edits []analysis.TextEdit
   219  			replace := func(exprs []ast.Expr, new string) {
   220  				for _, expr := range exprs {
   221  					edits = append(edits, analysis.TextEdit{
   222  						Pos:     expr.Pos(),
   223  						End:     expr.End(),
   224  						NewText: []byte(new),
   225  					})
   226  				}
   227  			}
   228  			// Get the ident for the call to strings.Index, which could just be
   229  			// "Index" if the strings package is dot imported.
   230  			indexCallId := typesinternal.UsedIdent(info, indexCall.Fun)
   231  			replacedFunc := "Cut"
   232  			if isContains {
   233  				replacedFunc = "Contains"
   234  				replace(negative, "!"+foundVarName) // idx < 0   ->  !found
   235  				replace(nonnegative, foundVarName)  // idx > -1  ->   found
   236  
   237  				// Replace the assignment with found, and replace the call to
   238  				// Index or IndexByte with a call to Contains.
   239  				// i     := strings.Index   (...)
   240  				// -----            --------
   241  				// found := strings.Contains(...)
   242  				edits = append(edits, analysis.TextEdit{
   243  					Pos:     iIdent.Pos(),
   244  					End:     iIdent.End(),
   245  					NewText: []byte(foundVarName),
   246  				}, analysis.TextEdit{
   247  					Pos:     indexCallId.Pos(),
   248  					End:     indexCallId.End(),
   249  					NewText: []byte("Contains"),
   250  				})
   251  			} else {
   252  				replace(negative, "!"+okVarName)    // idx < 0   ->  !ok
   253  				replace(nonnegative, okVarName)     // idx > -1  ->   ok
   254  				replace(beforeSlice, beforeVarName) // s[:idx]   ->   before
   255  				replace(afterSlice, afterVarName)   // s[idx+k:] ->   after
   256  
   257  				// Replace the assignment with before, after, ok, and replace
   258  				// the call to Index or IndexByte with a call to Cut.
   259  				// i     			 := strings.Index(...)
   260  				// -----------------            -----
   261  				// before, after, ok := strings.Cut  (...)
   262  				edits = append(edits, analysis.TextEdit{
   263  					Pos:     iIdent.Pos(),
   264  					End:     iIdent.End(),
   265  					NewText: fmt.Appendf(nil, "%s, %s, %s", beforeVarName, afterVarName, okVarName),
   266  				}, analysis.TextEdit{
   267  					Pos:     indexCallId.Pos(),
   268  					End:     indexCallId.End(),
   269  					NewText: []byte("Cut"),
   270  				})
   271  			}
   272  
   273  			// Calls to IndexByte have a byte as their second arg, which
   274  			// must be converted to a string or []byte to be a valid arg for Cut/Contains.
   275  			if obj.Name() == "IndexByte" {
   276  				switch obj.Pkg().Name() {
   277  				case "strings":
   278  					searchByteVal := info.Types[substr].Value
   279  					if searchByteVal == nil {
   280  						// substr is a variable, e.g. substr := byte('b')
   281  						// use string(substr)
   282  						edits = append(edits, []analysis.TextEdit{
   283  							{
   284  								Pos:     substr.Pos(),
   285  								NewText: []byte("string("),
   286  							},
   287  							{
   288  								Pos:     substr.End(),
   289  								NewText: []byte(")"),
   290  							},
   291  						}...)
   292  					} else {
   293  						// substr is a byte constant
   294  						val, _ := constant.Int64Val(searchByteVal) // inv: must be a valid byte
   295  						// strings.Cut/Contains requires a string, so convert byte literal to string literal; e.g. 'a' -> "a", 55 -> "7"
   296  						edits = append(edits, analysis.TextEdit{
   297  							Pos:     substr.Pos(),
   298  							End:     substr.End(),
   299  							NewText: strconv.AppendQuote(nil, string(byte(val))),
   300  						})
   301  					}
   302  				case "bytes":
   303  					// bytes.Cut/Contains requires a []byte, so wrap substr in a []byte{}
   304  					edits = append(edits, []analysis.TextEdit{
   305  						{
   306  							Pos:     substr.Pos(),
   307  							NewText: []byte("[]byte{"),
   308  						},
   309  						{
   310  							Pos:     substr.End(),
   311  							NewText: []byte("}"),
   312  						},
   313  					}...)
   314  				}
   315  			}
   316  			pass.Report(analysis.Diagnostic{
   317  				Pos: indexCall.Fun.Pos(),
   318  				End: indexCall.Fun.End(),
   319  				Message: fmt.Sprintf("%s.%s can be simplified using %s.%s",
   320  					obj.Pkg().Name(), obj.Name(), obj.Pkg().Name(), replacedFunc),
   321  				Category: "stringscut",
   322  				SuggestedFixes: []analysis.SuggestedFix{{
   323  					Message:   fmt.Sprintf("Simplify %s.%s call using %s.%s", obj.Pkg().Name(), obj.Name(), obj.Pkg().Name(), replacedFunc),
   324  					TextEdits: edits,
   325  				}},
   326  			})
   327  		}
   328  	}
   329  
   330  	return nil, nil
   331  }
   332  
   333  // indexArgValid reports whether expr is a valid strings.Index(_, _) arg
   334  // for the transformation. An arg is valid iff it is:
   335  // - constant;
   336  // - a local variable with no modifying uses after the Index() call; or
   337  // - []byte(x) where x is also valid by this definition.
   338  // All other expressions are assumed not referentially transparent,
   339  // so we cannot be sure that all uses are safe to replace.
   340  func indexArgValid(info *types.Info, index *typeindex.Index, expr ast.Expr, afterPos token.Pos) bool {
   341  	tv := info.Types[expr]
   342  	if tv.Value != nil {
   343  		return true // constant
   344  	}
   345  	switch expr := expr.(type) {
   346  	case *ast.CallExpr:
   347  		return types.Identical(tv.Type, byteSliceType) &&
   348  			indexArgValid(info, index, expr.Args[0], afterPos) // check s in []byte(s)
   349  	case *ast.Ident:
   350  		sObj := info.Uses[expr]
   351  		sUses := index.Uses(sObj)
   352  		return !hasModifyingUses(info, sUses, afterPos)
   353  	default:
   354  		// For now, skip instances where s or substr are not
   355  		// identifers, basic lits, or call expressions of the form
   356  		// []byte(s).
   357  		// TODO(mkalil): Handle s and substr being expressions like ptr.field[i].
   358  		// From adonovan: We'd need to analyze s and substr to see
   359  		// whether they are referentially transparent, and if not,
   360  		// analyze all code between declaration and use and see if
   361  		// there are statements or expressions with potential side
   362  		// effects.
   363  		return false
   364  	}
   365  }
   366  
   367  // checkIdxUses inspects the uses of i to make sure they match certain criteria that
   368  // allows us to suggest a modernization. If all uses of i, s and substr match
   369  // one of the following four valid formats, it returns a list of occurrences for
   370  // each format. If any of the uses do not match one of the formats, return nil
   371  // for all values, since we should not offer a replacement.
   372  // 1. negative - a condition equivalent to i < 0
   373  // 2. nonnegative - a condition equivalent to i >= 0
   374  // 3. beforeSlice - a slice of `s` that matches either s[:i], s[0:i]
   375  // 4. afterSlice - a slice of `s` that matches one of: s[i+len(substr):], s[len(substr) + i:], s[i + const], s[k + i] (where k = len(substr))
   376  func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr ast.Expr) (negative, nonnegative, beforeSlice, afterSlice []ast.Expr) {
   377  	use := func(cur inspector.Cursor) bool {
   378  		ek, _ := cur.ParentEdge()
   379  		n := cur.Parent().Node()
   380  		switch ek {
   381  		case edge.BinaryExpr_X, edge.BinaryExpr_Y:
   382  			check := n.(*ast.BinaryExpr)
   383  			switch checkIdxComparison(info, check) {
   384  			case -1:
   385  				negative = append(negative, check)
   386  				return true
   387  			case 1:
   388  				nonnegative = append(nonnegative, check)
   389  				return true
   390  			}
   391  			// Check is not equivalent to that i < 0 or i >= 0.
   392  			// Might be part of an outer slice expression like s[i + k]
   393  			// which requires a different check.
   394  			// Check that the thing being sliced is s and that the slice
   395  			// doesn't have a max index.
   396  			if slice, ok := cur.Parent().Parent().Node().(*ast.SliceExpr); ok &&
   397  				sameObject(info, s, slice.X) &&
   398  				slice.Max == nil {
   399  				if isBeforeSlice(info, ek, slice) {
   400  					beforeSlice = append(beforeSlice, slice)
   401  					return true
   402  				} else if isAfterSlice(info, ek, slice, substr) {
   403  					afterSlice = append(afterSlice, slice)
   404  					return true
   405  				}
   406  			}
   407  		case edge.SliceExpr_Low, edge.SliceExpr_High:
   408  			slice := n.(*ast.SliceExpr)
   409  			// Check that the thing being sliced is s and that the slice doesn't
   410  			// have a max index.
   411  			if sameObject(info, s, slice.X) && slice.Max == nil {
   412  				if isBeforeSlice(info, ek, slice) {
   413  					beforeSlice = append(beforeSlice, slice)
   414  					return true
   415  				} else if isAfterSlice(info, ek, slice, substr) {
   416  					afterSlice = append(afterSlice, slice)
   417  					return true
   418  				}
   419  			}
   420  		}
   421  		return false
   422  	}
   423  
   424  	for curIdent := range uses {
   425  		if !use(curIdent) {
   426  			return nil, nil, nil, nil
   427  		}
   428  	}
   429  	return negative, nonnegative, beforeSlice, afterSlice
   430  }
   431  
   432  // hasModifyingUses reports whether any of the uses involve potential
   433  // modifications. Uses involving assignments before the "afterPos" won't be
   434  // considered.
   435  func hasModifyingUses(info *types.Info, uses iter.Seq[inspector.Cursor], afterPos token.Pos) bool {
   436  	for curUse := range uses {
   437  		ek, _ := curUse.ParentEdge()
   438  		if ek == edge.AssignStmt_Lhs {
   439  			if curUse.Node().Pos() <= afterPos {
   440  				continue
   441  			}
   442  			assign := curUse.Parent().Node().(*ast.AssignStmt)
   443  			if sameObject(info, assign.Lhs[0], curUse.Node().(*ast.Ident)) {
   444  				// Modifying use because we are reassigning the value of the object.
   445  				return true
   446  			}
   447  		} else if ek == edge.UnaryExpr_X &&
   448  			curUse.Parent().Node().(*ast.UnaryExpr).Op == token.AND {
   449  			// Modifying use because we might be passing the object by reference (an explicit &).
   450  			// We can ignore the case where we have a method call on the expression (which
   451  			// has an implicit &) because we know the type of s and substr are strings
   452  			// which cannot have methods on them.
   453  			return true
   454  		}
   455  	}
   456  	return false
   457  }
   458  
   459  // checkIdxComparison reports whether the check is equivalent to i < 0 or its negation, or neither.
   460  // For equivalent to i >= 0, we only accept this exact BinaryExpr since
   461  // expressions like i > 0 or i >= 1 make a stronger statement about the value of i.
   462  // We avoid suggesting a fix in this case since it may result in an invalid
   463  // transformation (See golang/go#76687).
   464  // Since strings.Index returns exactly -1 if the substring is not found, we
   465  // don't need to handle expressions like i <= -3.
   466  // We return 0 if the expression does not match any of these options.
   467  // We assume that a check passed to checkIdxComparison has i as one of its operands.
   468  func checkIdxComparison(info *types.Info, check *ast.BinaryExpr) int {
   469  	// Ensure that the constant (if any) is on the right.
   470  	x, op, y := check.X, check.Op, check.Y
   471  	if info.Types[x].Value != nil {
   472  		x, op, y = y, flip(op), x
   473  	}
   474  
   475  	yIsInt := func(k int64) bool {
   476  		return isIntLiteral(info, y, k)
   477  	}
   478  
   479  	if op == token.LSS && yIsInt(0) || // i < 0
   480  		op == token.EQL && yIsInt(-1) || // i == -1
   481  		op == token.LEQ && yIsInt(-1) { // i <= -1
   482  		return -1 // check <=> i is negative
   483  	}
   484  
   485  	if op == token.GEQ && yIsInt(0) || // i >= 0
   486  		op == token.NEQ && yIsInt(-1) || // i != -1
   487  		op == token.GTR && yIsInt(-1) { // i > -1
   488  		return +1 // check <=> i is non-negative
   489  	}
   490  
   491  	return 0 // unknown
   492  }
   493  
   494  // flip changes the comparison token as if the operands were flipped.
   495  // It is defined only for == and the four inequalities.
   496  func flip(op token.Token) token.Token {
   497  	switch op {
   498  	case token.EQL:
   499  		return token.EQL // (same)
   500  	case token.GEQ:
   501  		return token.LEQ
   502  	case token.GTR:
   503  		return token.LSS
   504  	case token.LEQ:
   505  		return token.GEQ
   506  	case token.LSS:
   507  		return token.GTR
   508  	}
   509  	return op
   510  }
   511  
   512  // isBeforeSlice reports whether the SliceExpr is of the form s[:i] or s[0:i].
   513  func isBeforeSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr) bool {
   514  	return ek == edge.SliceExpr_High && (slice.Low == nil || isZeroIntConst(info, slice.Low))
   515  }
   516  
   517  // isAfterSlice reports whether the SliceExpr is of the form s[i+len(substr):],
   518  // or s[i + k:] where k is a const is equal to len(substr).
   519  func isAfterSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr, substr ast.Expr) bool {
   520  	lowExpr, ok := slice.Low.(*ast.BinaryExpr)
   521  	if !ok || slice.High != nil {
   522  		return false
   523  	}
   524  	// Returns true if the expression is a call to len(substr).
   525  	isLenCall := func(expr ast.Expr) bool {
   526  		call, ok := expr.(*ast.CallExpr)
   527  		if !ok || len(call.Args) != 1 {
   528  			return false
   529  		}
   530  		return sameObject(info, substr, call.Args[0]) && typeutil.Callee(info, call) == builtinLen
   531  	}
   532  
   533  	// Handle len([]byte(substr))
   534  	if is[*ast.CallExpr](substr) {
   535  		call := substr.(*ast.CallExpr)
   536  		tv := info.Types[call.Fun]
   537  		if tv.IsType() && types.Identical(tv.Type, byteSliceType) {
   538  			// Only one arg in []byte conversion.
   539  			substr = call.Args[0]
   540  		}
   541  	}
   542  	substrLen := -1
   543  	substrVal := info.Types[substr].Value
   544  	if substrVal != nil {
   545  		switch substrVal.Kind() {
   546  		case constant.String:
   547  			substrLen = len(constant.StringVal(substrVal))
   548  		case constant.Int:
   549  			// constant.Value is a byte literal, e.g. bytes.IndexByte(_, 'a')
   550  			// or a numeric byte literal, e.g. bytes.IndexByte(_, 65)
   551  			substrLen = 1
   552  		}
   553  	}
   554  
   555  	switch ek {
   556  	case edge.BinaryExpr_X:
   557  		kVal := info.Types[lowExpr.Y].Value
   558  		if kVal == nil {
   559  			// i + len(substr)
   560  			return lowExpr.Op == token.ADD && isLenCall(lowExpr.Y)
   561  		} else {
   562  			// i + k
   563  			kInt, ok := constant.Int64Val(kVal)
   564  			return ok && substrLen == int(kInt)
   565  		}
   566  	case edge.BinaryExpr_Y:
   567  		kVal := info.Types[lowExpr.X].Value
   568  		if kVal == nil {
   569  			// len(substr) + i
   570  			return lowExpr.Op == token.ADD && isLenCall(lowExpr.X)
   571  		} else {
   572  			// k + i
   573  			kInt, ok := constant.Int64Val(kVal)
   574  			return ok && substrLen == int(kInt)
   575  		}
   576  	}
   577  	return false
   578  }
   579  
   580  // sameObject reports whether we know that the expressions resolve to the same object.
   581  func sameObject(info *types.Info, expr1, expr2 ast.Expr) bool {
   582  	if ident1, ok := expr1.(*ast.Ident); ok {
   583  		if ident2, ok := expr2.(*ast.Ident); ok {
   584  			uses1, ok1 := info.Uses[ident1]
   585  			uses2, ok2 := info.Uses[ident2]
   586  			return ok1 && ok2 && uses1 == uses2
   587  		}
   588  	}
   589  	return false
   590  }
   591  

View as plain text