golang.org/x/tools/gopls@v0.15.3/internal/golang/completion/statements.go (about)

     1  // Copyright 2020 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 completion
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/token"
    11  	"go/types"
    12  	"strings"
    13  
    14  	"golang.org/x/tools/gopls/internal/cache"
    15  	"golang.org/x/tools/gopls/internal/golang"
    16  	"golang.org/x/tools/gopls/internal/golang/completion/snippet"
    17  	"golang.org/x/tools/gopls/internal/protocol"
    18  )
    19  
    20  // addStatementCandidates adds full statement completion candidates
    21  // appropriate for the current context.
    22  func (c *completer) addStatementCandidates() {
    23  	c.addErrCheck()
    24  	c.addAssignAppend()
    25  	c.addReturnZeroValues()
    26  }
    27  
    28  // addAssignAppend offers a completion candidate of the form:
    29  //
    30  //	someSlice = append(someSlice, )
    31  //
    32  // It will offer the "append" completion in either of two situations:
    33  //
    34  //  1. Position is in RHS of assign, prefix matches "append", and
    35  //     corresponding LHS object is a slice. For example,
    36  //     "foo = ap<>" completes to "foo = append(foo, )".
    37  //
    38  //  2. Prefix is an ident or selector in an *ast.ExprStmt (i.e.
    39  //     beginning of statement), and our best matching candidate is a
    40  //     slice. For example: "foo.ba" completes to "foo.bar = append(foo.bar, )".
    41  func (c *completer) addAssignAppend() {
    42  	if len(c.path) < 3 {
    43  		return
    44  	}
    45  
    46  	ident, _ := c.path[0].(*ast.Ident)
    47  	if ident == nil {
    48  		return
    49  	}
    50  
    51  	var (
    52  		// sliceText is the full name of our slice object, e.g. "s.abc" in
    53  		// "s.abc = app<>".
    54  		sliceText string
    55  		// needsLHS is true if we need to prepend the LHS slice name and
    56  		// "=" to our candidate.
    57  		needsLHS = false
    58  		fset     = c.pkg.FileSet()
    59  	)
    60  
    61  	switch n := c.path[1].(type) {
    62  	case *ast.AssignStmt:
    63  		// We are already in an assignment. Make sure our prefix matches "append".
    64  		if c.matcher.Score("append") <= 0 {
    65  			return
    66  		}
    67  
    68  		exprIdx := exprAtPos(c.pos, n.Rhs)
    69  		if exprIdx == len(n.Rhs) || exprIdx > len(n.Lhs)-1 {
    70  			return
    71  		}
    72  
    73  		lhsType := c.pkg.GetTypesInfo().TypeOf(n.Lhs[exprIdx])
    74  		if lhsType == nil {
    75  			return
    76  		}
    77  
    78  		// Make sure our corresponding LHS object is a slice.
    79  		if _, isSlice := lhsType.Underlying().(*types.Slice); !isSlice {
    80  			return
    81  		}
    82  
    83  		// The name or our slice is whatever's in the LHS expression.
    84  		sliceText = golang.FormatNode(fset, n.Lhs[exprIdx])
    85  	case *ast.SelectorExpr:
    86  		// Make sure we are a selector at the beginning of a statement.
    87  		if _, parentIsExprtStmt := c.path[2].(*ast.ExprStmt); !parentIsExprtStmt {
    88  			return
    89  		}
    90  
    91  		// So far we only know the first part of our slice name. For
    92  		// example in "s.a<>" we only know our slice begins with "s."
    93  		// since the user could still be typing.
    94  		sliceText = golang.FormatNode(fset, n.X) + "."
    95  		needsLHS = true
    96  	case *ast.ExprStmt:
    97  		needsLHS = true
    98  	default:
    99  		return
   100  	}
   101  
   102  	var (
   103  		label string
   104  		snip  snippet.Builder
   105  		score = highScore
   106  	)
   107  
   108  	if needsLHS {
   109  		// Offer the long form assign + append candidate if our best
   110  		// candidate is a slice.
   111  		bestItem := c.topCandidate()
   112  		if bestItem == nil || !bestItem.isSlice {
   113  			return
   114  		}
   115  
   116  		// Don't rank the full form assign + append candidate above the
   117  		// slice itself.
   118  		score = bestItem.Score - 0.01
   119  
   120  		// Fill in rest of sliceText now that we have the object name.
   121  		sliceText += bestItem.Label
   122  
   123  		// Fill in the candidate's LHS bits.
   124  		label = fmt.Sprintf("%s = ", bestItem.Label)
   125  		snip.WriteText(label)
   126  	}
   127  
   128  	snip.WriteText(fmt.Sprintf("append(%s, ", sliceText))
   129  	snip.WritePlaceholder(nil)
   130  	snip.WriteText(")")
   131  
   132  	c.items = append(c.items, CompletionItem{
   133  		Label:   label + fmt.Sprintf("append(%s, )", sliceText),
   134  		Kind:    protocol.FunctionCompletion,
   135  		Score:   score,
   136  		snippet: &snip,
   137  	})
   138  }
   139  
   140  // topCandidate returns the strictly highest scoring candidate
   141  // collected so far. If the top two candidates have the same score,
   142  // nil is returned.
   143  func (c *completer) topCandidate() *CompletionItem {
   144  	var bestItem, secondBestItem *CompletionItem
   145  	for i := range c.items {
   146  		if bestItem == nil || c.items[i].Score > bestItem.Score {
   147  			bestItem = &c.items[i]
   148  		} else if secondBestItem == nil || c.items[i].Score > secondBestItem.Score {
   149  			secondBestItem = &c.items[i]
   150  		}
   151  	}
   152  
   153  	// If secondBestItem has the same score, bestItem isn't
   154  	// the strict best.
   155  	if secondBestItem != nil && secondBestItem.Score == bestItem.Score {
   156  		return nil
   157  	}
   158  
   159  	return bestItem
   160  }
   161  
   162  // addErrCheck offers a completion candidate of the form:
   163  //
   164  //	if err != nil {
   165  //	  return nil, err
   166  //	}
   167  //
   168  // In the case of test functions, it offers a completion candidate of the form:
   169  //
   170  //	if err != nil {
   171  //	  t.Fatal(err)
   172  //	}
   173  //
   174  // The position must be in a function that returns an error, and the
   175  // statement preceding the position must be an assignment where the
   176  // final LHS object is an error. addErrCheck will synthesize
   177  // zero values as necessary to make the return statement valid.
   178  func (c *completer) addErrCheck() {
   179  	if len(c.path) < 2 || c.enclosingFunc == nil || !c.opts.placeholders {
   180  		return
   181  	}
   182  
   183  	var (
   184  		errorType        = types.Universe.Lookup("error").Type()
   185  		result           = c.enclosingFunc.sig.Results()
   186  		testVar          = getTestVar(c.enclosingFunc, c.pkg)
   187  		isTest           = testVar != ""
   188  		doesNotReturnErr = result.Len() == 0 || !types.Identical(result.At(result.Len()-1).Type(), errorType)
   189  	)
   190  	// Make sure our enclosing function is a Test func or returns an error.
   191  	if !isTest && doesNotReturnErr {
   192  		return
   193  	}
   194  
   195  	prevLine := prevStmt(c.pos, c.path)
   196  	if prevLine == nil {
   197  		return
   198  	}
   199  
   200  	// Make sure our preceding statement was as assignment.
   201  	assign, _ := prevLine.(*ast.AssignStmt)
   202  	if assign == nil || len(assign.Lhs) == 0 {
   203  		return
   204  	}
   205  
   206  	lastAssignee := assign.Lhs[len(assign.Lhs)-1]
   207  
   208  	// Make sure the final assignee is an error.
   209  	if !types.Identical(c.pkg.GetTypesInfo().TypeOf(lastAssignee), errorType) {
   210  		return
   211  	}
   212  
   213  	var (
   214  		// errVar is e.g. "err" in "foo, err := bar()".
   215  		errVar = golang.FormatNode(c.pkg.FileSet(), lastAssignee)
   216  
   217  		// Whether we need to include the "if" keyword in our candidate.
   218  		needsIf = true
   219  	)
   220  
   221  	// If the returned error from the previous statement is "_", it is not a real object.
   222  	// If we don't have an error, and the function signature takes a testing.TB that is either ignored
   223  	// or an "_", then we also can't call t.Fatal(err).
   224  	if errVar == "_" {
   225  		return
   226  	}
   227  
   228  	// Below we try to detect if the user has already started typing "if
   229  	// err" so we can replace what they've typed with our complete
   230  	// statement.
   231  	switch n := c.path[0].(type) {
   232  	case *ast.Ident:
   233  		switch c.path[1].(type) {
   234  		case *ast.ExprStmt:
   235  			// This handles:
   236  			//
   237  			//     f, err := os.Open("foo")
   238  			//     i<>
   239  
   240  			// Make sure they are typing "if".
   241  			if c.matcher.Score("if") <= 0 {
   242  				return
   243  			}
   244  		case *ast.IfStmt:
   245  			// This handles:
   246  			//
   247  			//     f, err := os.Open("foo")
   248  			//     if er<>
   249  
   250  			// Make sure they are typing the error's name.
   251  			if c.matcher.Score(errVar) <= 0 {
   252  				return
   253  			}
   254  
   255  			needsIf = false
   256  		default:
   257  			return
   258  		}
   259  	case *ast.IfStmt:
   260  		// This handles:
   261  		//
   262  		//     f, err := os.Open("foo")
   263  		//     if <>
   264  
   265  		// Avoid false positives by ensuring the if's cond is a bad
   266  		// expression. For example, don't offer the completion in cases
   267  		// like "if <> somethingElse".
   268  		if _, bad := n.Cond.(*ast.BadExpr); !bad {
   269  			return
   270  		}
   271  
   272  		// If "if" is our direct prefix, we need to include it in our
   273  		// candidate since the existing "if" will be overwritten.
   274  		needsIf = c.pos == n.Pos()+token.Pos(len("if"))
   275  	}
   276  
   277  	// Build up a snippet that looks like:
   278  	//
   279  	//     if err != nil {
   280  	//       return <zero value>, ..., ${1:err}
   281  	//     }
   282  	//
   283  	// We make the error a placeholder so it is easy to alter the error.
   284  	var snip snippet.Builder
   285  	if needsIf {
   286  		snip.WriteText("if ")
   287  	}
   288  	snip.WriteText(fmt.Sprintf("%s != nil {\n\t", errVar))
   289  
   290  	var label string
   291  	if isTest {
   292  		snip.WriteText(fmt.Sprintf("%s.Fatal(%s)", testVar, errVar))
   293  		label = fmt.Sprintf("%[1]s != nil { %[2]s.Fatal(%[1]s) }", errVar, testVar)
   294  	} else {
   295  		snip.WriteText("return ")
   296  		for i := 0; i < result.Len()-1; i++ {
   297  			snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf))
   298  			snip.WriteText(", ")
   299  		}
   300  		snip.WritePlaceholder(func(b *snippet.Builder) {
   301  			b.WriteText(errVar)
   302  		})
   303  		label = fmt.Sprintf("%[1]s != nil { return %[1]s }", errVar)
   304  	}
   305  
   306  	snip.WriteText("\n}")
   307  
   308  	if needsIf {
   309  		label = "if " + label
   310  	}
   311  
   312  	c.items = append(c.items, CompletionItem{
   313  		Label:   label,
   314  		Kind:    protocol.SnippetCompletion,
   315  		Score:   highScore,
   316  		snippet: &snip,
   317  	})
   318  }
   319  
   320  // getTestVar checks the function signature's input parameters and returns
   321  // the name of the first parameter that implements "testing.TB". For example,
   322  // func someFunc(t *testing.T) returns the string "t", func someFunc(b *testing.B)
   323  // returns "b" etc. An empty string indicates that the function signature
   324  // does not take a testing.TB parameter or does so but is ignored such
   325  // as func someFunc(*testing.T).
   326  func getTestVar(enclosingFunc *funcInfo, pkg *cache.Package) string {
   327  	if enclosingFunc == nil || enclosingFunc.sig == nil {
   328  		return ""
   329  	}
   330  
   331  	var testingPkg *types.Package
   332  	for _, p := range pkg.GetTypes().Imports() {
   333  		if p.Path() == "testing" {
   334  			testingPkg = p
   335  			break
   336  		}
   337  	}
   338  	if testingPkg == nil {
   339  		return ""
   340  	}
   341  	tbObj := testingPkg.Scope().Lookup("TB")
   342  	if tbObj == nil {
   343  		return ""
   344  	}
   345  	iface, ok := tbObj.Type().Underlying().(*types.Interface)
   346  	if !ok {
   347  		return ""
   348  	}
   349  
   350  	sig := enclosingFunc.sig
   351  	for i := 0; i < sig.Params().Len(); i++ {
   352  		param := sig.Params().At(i)
   353  		if param.Name() == "_" {
   354  			continue
   355  		}
   356  		if !types.Implements(param.Type(), iface) {
   357  			continue
   358  		}
   359  		return param.Name()
   360  	}
   361  
   362  	return ""
   363  }
   364  
   365  // addReturnZeroValues offers a snippet candidate on the form:
   366  //
   367  //	return 0, "", nil
   368  //
   369  // Requires a partially or fully written return keyword at position.
   370  // Requires current position to be in a function with more than
   371  // zero return parameters.
   372  func (c *completer) addReturnZeroValues() {
   373  	if len(c.path) < 2 || c.enclosingFunc == nil || !c.opts.placeholders {
   374  		return
   375  	}
   376  	result := c.enclosingFunc.sig.Results()
   377  	if result.Len() == 0 {
   378  		return
   379  	}
   380  
   381  	// Offer just less than we expect from return as a keyword.
   382  	var score = stdScore - 0.01
   383  	switch c.path[0].(type) {
   384  	case *ast.ReturnStmt, *ast.Ident:
   385  		f := c.matcher.Score("return")
   386  		if f <= 0 {
   387  			return
   388  		}
   389  		score *= float64(f)
   390  	default:
   391  		return
   392  	}
   393  
   394  	// The snippet will have a placeholder over each return value.
   395  	// The label will not.
   396  	var snip snippet.Builder
   397  	var label strings.Builder
   398  	snip.WriteText("return ")
   399  	fmt.Fprintf(&label, "return ")
   400  
   401  	for i := 0; i < result.Len(); i++ {
   402  		if i > 0 {
   403  			snip.WriteText(", ")
   404  			fmt.Fprintf(&label, ", ")
   405  		}
   406  
   407  		zero := formatZeroValue(result.At(i).Type(), c.qf)
   408  		snip.WritePlaceholder(func(b *snippet.Builder) {
   409  			b.WriteText(zero)
   410  		})
   411  		fmt.Fprintf(&label, zero)
   412  	}
   413  
   414  	c.items = append(c.items, CompletionItem{
   415  		Label:   label.String(),
   416  		Kind:    protocol.SnippetCompletion,
   417  		Score:   score,
   418  		snippet: &snip,
   419  	})
   420  }