internal/lsp/analysis: move implementmissing logic into undeclared

I missed the TODO in undeclaredname to add support for functions, so
really this belongs in that analyzer. This removes a fair bit of code.
However, the type error analyzers don't really work with the go/analysis
testing framework because the suggested fixes are split from the
diagnostics, so I moved the tests into the gopls tests.

Change-Id: I861a7ad531d2732fe698ee0ac46f23ad53b16812
Reviewed-on: https://go-review.googlesource.com/c/tools/+/351333
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md
index 8215f1b..cc344b8 100644
--- a/gopls/doc/analyzers.md
+++ b/gopls/doc/analyzers.md
@@ -579,25 +579,6 @@
 
 **Enabled by default.**
 
-## **implementmissing**
-
-suggested fixes for "undeclared name: %s" on a function call
-
-This checker provides suggested fixes for type errors of the
-type "undeclared name: %s" that happen for a function call. For example:
-	func m() {
-	  a(1)
-	}
-will turn into
-	func m() {
-	  a(1)
-	}
-
-	func a(i int) {}
-
-
-**Disabled by default. Enable it by setting `"analyses": {"implementmissing": true}`.**
-
 ## **nonewvars**
 
 suggested fixes for "no new vars on left side of :="
@@ -631,8 +612,17 @@
 suggested fixes for "undeclared name: <>"
 
 This checker provides suggested fixes for type errors of the
-type "undeclared name: <>". It will insert a new statement:
-"<> := ".
+type "undeclared name: <>". It will either insert a new statement,
+such as:
+
+"<> := "
+
+or a new function declaration, such as:
+
+func <>(inferred parameters) {
+	panic("implement me!")
+}
+
 
 **Enabled by default.**
 
diff --git a/internal/lsp/analysis/implementmissing/missingfunc.go b/internal/lsp/analysis/implementmissing/missingfunc.go
deleted file mode 100644
index 2e4bc3c..0000000
--- a/internal/lsp/analysis/implementmissing/missingfunc.go
+++ /dev/null
@@ -1,274 +0,0 @@
-// 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 implementmissing defines an Analyzer that will attempt to
-// automatically implement a function that is currently undeclared.
-package implementmissing
-
-import (
-	"bytes"
-	"fmt"
-	"go/ast"
-	"go/format"
-	"go/types"
-	"strings"
-	"unicode"
-
-	"golang.org/x/tools/go/analysis"
-	"golang.org/x/tools/go/ast/astutil"
-	"golang.org/x/tools/internal/analysisinternal"
-)
-
-const Doc = `suggested fixes for "undeclared name: %s" on a function call
-
-This checker provides suggested fixes for type errors of the
-type "undeclared name: %s" that happen for a function call. For example:
-	func m() {
-	  a(1)
-	}
-will turn into
-	func m() {
-	  a(1)
-	}
-
-	func a(i int) {}
-`
-
-var Analyzer = &analysis.Analyzer{
-	Name:             "implementmissing",
-	Doc:              Doc,
-	Requires:         []*analysis.Analyzer{},
-	Run:              run,
-	RunDespiteErrors: true,
-}
-
-const undeclaredNamePrefix = "undeclared name: "
-
-func run(pass *analysis.Pass) (interface{}, error) {
-	info := pass.TypesInfo
-	if info == nil {
-		return nil, fmt.Errorf("nil TypeInfo")
-	}
-
-	errors := analysisinternal.GetTypeErrors(pass)
-	for _, typeErr := range errors {
-		// Filter out the errors that are not relevant to this analyzer.
-		if !FixesError(typeErr.Msg) {
-			continue
-		}
-
-		var file *ast.File
-		for _, f := range pass.Files {
-			if f.Pos() <= typeErr.Pos && typeErr.Pos <= f.End() {
-				file = f
-				break
-			}
-		}
-		if file == nil {
-			continue
-		}
-
-		var buf bytes.Buffer
-		if err := format.Node(&buf, pass.Fset, file); err != nil {
-			continue
-		}
-		typeErrEndPos := analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), typeErr.Pos)
-
-		// Get the path for the relevant range.
-		path, _ := astutil.PathEnclosingInterval(file, typeErr.Pos, typeErrEndPos)
-		if len(path) < 2 {
-			return nil, nil
-		}
-
-		// Check to make sure we're dealing with a function call, we don't want to
-		// deal with undeclared variables here.
-		call, ok := path[1].(*ast.CallExpr)
-		if !ok {
-			return nil, nil
-		}
-
-		ident, ok := path[0].(*ast.Ident)
-		if !ok {
-			return nil, nil
-		}
-
-		var paramNames []string
-		var paramTypes []types.Type
-
-		// keep track of all param names to later ensure uniqueness
-		namesCount := map[string]int{}
-
-		for _, arg := range call.Args {
-			ty := pass.TypesInfo.TypeOf(arg)
-			if ty == nil {
-				return nil, nil
-			}
-
-			switch t := ty.(type) {
-			// this is the case where another function call returning multiple
-			// results is used as an argument
-			case *types.Tuple:
-				n := t.Len()
-				for i := 0; i < n; i++ {
-					name := typeToArgName(t.At(i).Type())
-					namesCount[name]++
-
-					paramNames = append(paramNames, name)
-					paramTypes = append(paramTypes, types.Default(t.At(i).Type()))
-				}
-
-			default:
-				// does the argument have a name we can reuse?
-				// only happens in case of a *ast.Ident
-				var name string
-				if ident, ok := arg.(*ast.Ident); ok {
-					name = ident.Name
-				}
-
-				if name == "" {
-					name = typeToArgName(ty)
-				}
-
-				namesCount[name]++
-
-				paramNames = append(paramNames, name)
-				paramTypes = append(paramTypes, types.Default(ty))
-			}
-		}
-
-		for n, c := range namesCount {
-			// Any names we saw more than once will need a unique suffix added
-			// on. Reset the count to 1 to act as the suffix for the first
-			// occurrence of that name.
-			if c >= 2 {
-				namesCount[n] = 1
-			} else {
-				delete(namesCount, n)
-			}
-		}
-
-		params := &ast.FieldList{
-			List: []*ast.Field{},
-		}
-
-		for i, name := range paramNames {
-			if suffix, repeats := namesCount[name]; repeats {
-				namesCount[name]++
-				name = fmt.Sprintf("%s%d", name, suffix)
-			}
-
-			// only worth checking after previous param in the list
-			if i > 0 {
-				// if type of parameter at hand is the same as the previous one,
-				// add it to the previous param list of identifiers so to have:
-				//  (s1, s2 string)
-				// and not
-				//  (s1 string, s2 string)
-				if paramTypes[i] == paramTypes[i-1] {
-					params.List[len(params.List)-1].Names = append(params.List[len(params.List)-1].Names, ast.NewIdent(name))
-					continue
-				}
-			}
-
-			params.List = append(params.List, &ast.Field{
-				Names: []*ast.Ident{
-					ast.NewIdent(name),
-				},
-				Type: analysisinternal.TypeExpr(pass.Fset, file, pass.Pkg, paramTypes[i]),
-			})
-		}
-
-		eof := file.End()
-
-		decl := &ast.FuncDecl{
-			Name: &ast.Ident{
-				Name: ident.Name,
-			},
-			Type: &ast.FuncType{
-				Func:    file.End(),
-				Params:  params,
-				Results: &ast.FieldList{},
-			},
-			Body: &ast.BlockStmt{
-				List: []ast.Stmt{
-					&ast.ExprStmt{
-						X: &ast.CallExpr{
-							Fun: &ast.Ident{
-								Name: "panic",
-							},
-							Args: []ast.Expr{
-								&ast.BasicLit{
-									Value: `"not implemented"`,
-								},
-							},
-						},
-					},
-				},
-			},
-		}
-
-		var declBuf bytes.Buffer
-		if err := format.Node(&declBuf, pass.Fset, decl); err != nil {
-			return nil, err
-		}
-
-		text := append([]byte("\n\n"), declBuf.Bytes()...)
-		text = append(text, []byte("\n")...)
-
-		pass.Report(analysis.Diagnostic{
-			Pos:     typeErr.Pos,
-			End:     typeErr.Pos,
-			Message: typeErr.Msg,
-			SuggestedFixes: []analysis.SuggestedFix{
-				{
-					Message: "Implement function " + ident.Name,
-					TextEdits: []analysis.TextEdit{{
-						Pos:     eof,
-						End:     eof,
-						NewText: text,
-					}},
-				},
-			},
-			Related: []analysis.RelatedInformation{},
-		})
-	}
-	return nil, nil
-}
-
-func FixesError(msg string) bool {
-	return strings.HasPrefix(msg, undeclaredNamePrefix)
-}
-
-func typeToArgName(ty types.Type) string {
-	s := types.Default(ty).String()
-
-	switch t := ty.(type) {
-	case *types.Basic:
-		// use first letter in type name for basic types
-		return s[0:1]
-	case *types.Slice:
-		// use element type to decide var name for slices
-		return typeToArgName(t.Elem())
-	case *types.Array:
-		// use element type to decide var name for arrays
-		return typeToArgName(t.Elem())
-	case *types.Chan:
-		return "ch"
-	}
-
-	s = strings.TrimFunc(s, func(r rune) bool {
-		return !unicode.IsLetter(r)
-	})
-
-	if s == "error" {
-		return "err"
-	}
-
-	// remove package (if present)
-	// and make first letter lowercase
-	parts := strings.Split(s, ".")
-	a := []rune(parts[len(parts)-1])
-	a[0] = unicode.ToLower(a[0])
-	return string(a)
-}
diff --git a/internal/lsp/analysis/implementmissing/missingfunc_test.go b/internal/lsp/analysis/implementmissing/missingfunc_test.go
deleted file mode 100644
index 7c9b573..0000000
--- a/internal/lsp/analysis/implementmissing/missingfunc_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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 implementmissing_test
-
-import (
-	"testing"
-
-	"golang.org/x/tools/go/analysis/analysistest"
-	"golang.org/x/tools/internal/lsp/analysis/implementmissing"
-)
-
-func Test(t *testing.T) {
-	testdata := analysistest.TestData()
-	analysistest.RunWithSuggestedFixes(t, testdata, implementmissing.Analyzer, "missingfunction")
-}
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go.golden
deleted file mode 100644
index c8578e6..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go.golden
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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 missingfunction
-
-func channels(s string) {
-	undefinedChannels(c()) // want "undeclared name: undefinedChannels"
-}
-
-func c() (<-chan string, chan string) {
-	return make(<-chan string), make(chan string)
-}
-
-func undefinedChannels(ch1 <-chan string, ch2 chan string) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go.golden
deleted file mode 100644
index 050b0fc..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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 missingfunction
-
-func consecutiveParams() {
-	var s string
-	undefinedConsecutiveParams(s, s) // want "undeclared name: undefinedConsecutiveParams"
-}
-
-func undefinedConsecutiveParams(s1, s2 string) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go.golden
deleted file mode 100644
index 1661fc4..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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 missingfunction
-
-func errorParam() {
-	var err error
-	undefinedErrorParam(err) // want "undeclared name: undefinedErrorParam"
-}
-
-func undefinedErrorParam(err error) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go.golden
deleted file mode 100644
index ee32b71..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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 missingfunction
-
-type T struct{}
-
-func literals() {
-	undefinedLiterals("hey compiler", T{}, &T{}) // want "undeclared name: undefinedLiterals"
-}
-
-func undefinedLiterals(s string, t1 T, t2 *T) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go.golden
deleted file mode 100644
index 1bf35cf..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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 missingfunction
-
-import "time"
-
-func operation() {
-	undefinedOperation(10 * time.Second) // want "undeclared name: undefinedOperation"
-}
-
-func undefinedOperation(duration time.Duration) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go.golden
deleted file mode 100644
index 9d66f6a..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go.golden
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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 missingfunction
-
-func selector() {
-	m := map[int]bool{}
-	undefinedSelector(m[1]) // want "undeclared name: undefinedSelector"
-}
-
-func undefinedSelector(b bool) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go.golden
deleted file mode 100644
index 75f4578..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go.golden
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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 missingfunction
-
-func slice() {
-	undefinedSlice([]int{1, 2}) // want "undeclared name: undefinedSlice"
-}
-
-func undefinedSlice(i []int) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go.golden
deleted file mode 100644
index ac33ed0..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go.golden
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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 missingfunction
-
-func tuple() {
-	undefinedTuple(b()) // want "undeclared name: undefinedTuple"
-}
-
-func b() (string, error) {
-	return "", nil
-}
-
-func undefinedTuple(s string, err error) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go.golden b/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go.golden
deleted file mode 100644
index 28c27d8..0000000
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go.golden
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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 missingfunction
-
-func uniqueArguments() {
-	var s string
-	var i int
-	undefinedUniqueArguments(s, i, s) // want "undeclared name: undefinedUniqueArguments"
-}
-
-func undefinedUniqueArguments(s1 string, i int, s2 string) { panic("not implemented") }
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/channels.go
similarity index 93%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/channels.go
index fa91518..ecf00ec 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/channels.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/channels.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package missingfunction
+package undeclared
 
 func channels(s string) {
 	undefinedChannels(c()) // want "undeclared name: undefinedChannels"
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/consecutive_params.go
similarity index 92%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/consecutive_params.go
index 4a6ec62..ab7b2ba 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/consecutive_params.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/consecutive_params.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package missingfunction
+package undeclared
 
 func consecutiveParams() {
 	var s string
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/error_param.go
similarity index 91%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/error_param.go
index 49c5258..341a9d2 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/error_param.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/error_param.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package missingfunction
+package undeclared
 
 func errorParam() {
 	var err error
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/literals.go
similarity index 91%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/literals.go
index 46c045c..ab82463 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/literals.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/literals.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package missingfunction
+package undeclared
 
 type T struct{}
 
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/operation.go
similarity index 91%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/operation.go
index b8330ec..9a54382 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/operation.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/operation.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package missingfunction
+package undeclared
 
 import "time"
 
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/selector.go
similarity index 91%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/selector.go
index 5db716f..9ed09a2 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/selector.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/selector.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package missingfunction
+package undeclared
 
 func selector() {
 	m := map[int]bool{}
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/slice.go
similarity index 90%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/slice.go
index 179954c..d741c68 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/slice.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/slice.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package missingfunction
+package undeclared
 
 func slice() {
 	undefinedSlice([]int{1, 2}) // want "undeclared name: undefinedSlice"
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/tuple.go
similarity index 91%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/tuple.go
index 673f607..3148e8f 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/tuple.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/tuple.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package missingfunction
+package undeclared
 
 func tuple() {
 	undefinedTuple(b()) // want "undeclared name: undefinedTuple"
diff --git a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go b/internal/lsp/analysis/undeclaredname/testdata/src/a/unique_params.go
similarity index 92%
rename from internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go
rename to internal/lsp/analysis/undeclaredname/testdata/src/a/unique_params.go
index 2f18498..98f77a4 100644
--- a/internal/lsp/analysis/implementmissing/testdata/src/missingfunction/unique_params.go
+++ b/internal/lsp/analysis/undeclaredname/testdata/src/a/unique_params.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package missingfunction
+package undeclared
 
 func uniqueArguments() {
 	var s string
diff --git a/internal/lsp/analysis/undeclaredname/undeclared.go b/internal/lsp/analysis/undeclaredname/undeclared.go
index df24d1d..1e47706 100644
--- a/internal/lsp/analysis/undeclaredname/undeclared.go
+++ b/internal/lsp/analysis/undeclaredname/undeclared.go
@@ -10,9 +10,11 @@
 	"bytes"
 	"fmt"
 	"go/ast"
+	"go/format"
 	"go/token"
 	"go/types"
 	"strings"
+	"unicode"
 
 	"golang.org/x/tools/go/analysis"
 	"golang.org/x/tools/go/ast/astutil"
@@ -23,8 +25,17 @@
 const Doc = `suggested fixes for "undeclared name: <>"
 
 This checker provides suggested fixes for type errors of the
-type "undeclared name: <>". It will insert a new statement:
-"<> := ".`
+type "undeclared name: <>". It will either insert a new statement,
+such as:
+
+"<> := "
+
+or a new function declaration, such as:
+
+func <>(inferred parameters) {
+	panic("implement me!")
+}
+`
 
 var Analyzer = &analysis.Analyzer{
 	Name:             string(analysisinternal.UndeclaredName),
@@ -67,11 +78,6 @@
 		if _, ok := path[1].(*ast.SelectorExpr); ok {
 			continue
 		}
-		// TODO(golang.org/issue/34644): Handle call expressions with suggested
-		// fixes to create a function.
-		if _, ok := path[1].(*ast.CallExpr); ok {
-			continue
-		}
 		tok := pass.Fset.File(file.Pos())
 		if tok == nil {
 			continue
@@ -87,7 +93,7 @@
 	return nil, nil
 }
 
-func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, _ *types.Package, _ *types.Info) (*analysis.SuggestedFix, error) {
+func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
 	pos := rng.Start // don't use the end
 	path, _ := astutil.PathEnclosingInterval(file, pos, pos)
 	if len(path) < 2 {
@@ -97,6 +103,15 @@
 	if !ok {
 		return nil, fmt.Errorf("")
 	}
+
+	// Check for a possible call expression, in which case we should add a
+	// new function declaration.
+	if len(path) > 1 {
+		if _, ok := path[1].(*ast.CallExpr); ok {
+			return newFunctionDeclaration(path, file, pkg, info, fset)
+		}
+	}
+
 	// Get the place to insert the new statement.
 	insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
 	if insertBeforeStmt == nil {
@@ -111,6 +126,7 @@
 	if nl := bytes.LastIndex(contentBeforeStmt, []byte("\n")); nl != -1 {
 		indent = string(contentBeforeStmt[nl:])
 	}
+
 	// Create the new local variable statement.
 	newStmt := fmt.Sprintf("%s := %s", ident.Name, indent)
 	return &analysis.SuggestedFix{
@@ -126,3 +142,182 @@
 func FixesError(msg string) bool {
 	return strings.HasPrefix(msg, undeclaredNamePrefix)
 }
+
+func newFunctionDeclaration(path []ast.Node, file *ast.File, pkg *types.Package, info *types.Info, fset *token.FileSet) (*analysis.SuggestedFix, error) {
+	if len(path) < 3 {
+		return nil, fmt.Errorf("unexpected set of enclosing nodes: %v", path)
+	}
+	ident, ok := path[0].(*ast.Ident)
+	if !ok {
+		return nil, fmt.Errorf("no name for function declaration %v (%T)", path[0], path[0])
+	}
+	call, ok := path[1].(*ast.CallExpr)
+	if !ok {
+		return nil, fmt.Errorf("no call expression found %v (%T)", path[1], path[1])
+	}
+
+	// Find the enclosing function, so that we can add the new declaration
+	// below.
+	var enclosing *ast.FuncDecl
+	for _, n := range path {
+		if n, ok := n.(*ast.FuncDecl); ok {
+			enclosing = n
+			break
+		}
+	}
+	// TODO(rstambler): Support the situation when there is no enclosing
+	// function.
+	if enclosing == nil {
+		return nil, fmt.Errorf("no enclosing function found: %v", path)
+	}
+
+	pos := enclosing.End()
+
+	var paramNames []string
+	var paramTypes []types.Type
+	// keep track of all param names to later ensure uniqueness
+	nameCounts := map[string]int{}
+	for _, arg := range call.Args {
+		typ := info.TypeOf(arg)
+		if typ == nil {
+			return nil, fmt.Errorf("unable to determine type for %s", arg)
+		}
+
+		switch t := typ.(type) {
+		// this is the case where another function call returning multiple
+		// results is used as an argument
+		case *types.Tuple:
+			n := t.Len()
+			for i := 0; i < n; i++ {
+				name := typeToArgName(t.At(i).Type())
+				nameCounts[name]++
+
+				paramNames = append(paramNames, name)
+				paramTypes = append(paramTypes, types.Default(t.At(i).Type()))
+			}
+
+		default:
+			// does the argument have a name we can reuse?
+			// only happens in case of a *ast.Ident
+			var name string
+			if ident, ok := arg.(*ast.Ident); ok {
+				name = ident.Name
+			}
+
+			if name == "" {
+				name = typeToArgName(typ)
+			}
+
+			nameCounts[name]++
+
+			paramNames = append(paramNames, name)
+			paramTypes = append(paramTypes, types.Default(typ))
+		}
+	}
+
+	for n, c := range nameCounts {
+		// Any names we saw more than once will need a unique suffix added
+		// on. Reset the count to 1 to act as the suffix for the first
+		// occurrence of that name.
+		if c >= 2 {
+			nameCounts[n] = 1
+		} else {
+			delete(nameCounts, n)
+		}
+	}
+
+	params := &ast.FieldList{}
+
+	for i, name := range paramNames {
+		if suffix, repeats := nameCounts[name]; repeats {
+			nameCounts[name]++
+			name = fmt.Sprintf("%s%d", name, suffix)
+		}
+
+		// only worth checking after previous param in the list
+		if i > 0 {
+			// if type of parameter at hand is the same as the previous one,
+			// add it to the previous param list of identifiers so to have:
+			//  (s1, s2 string)
+			// and not
+			//  (s1 string, s2 string)
+			if paramTypes[i] == paramTypes[i-1] {
+				params.List[len(params.List)-1].Names = append(params.List[len(params.List)-1].Names, ast.NewIdent(name))
+				continue
+			}
+		}
+
+		params.List = append(params.List, &ast.Field{
+			Names: []*ast.Ident{
+				ast.NewIdent(name),
+			},
+			Type: analysisinternal.TypeExpr(fset, file, pkg, paramTypes[i]),
+		})
+	}
+
+	decl := &ast.FuncDecl{
+		Name: ast.NewIdent(ident.Name),
+		Type: &ast.FuncType{
+			Params: params,
+			// TODO(rstambler): Also handle result parameters here.
+		},
+		Body: &ast.BlockStmt{
+			List: []ast.Stmt{
+				&ast.ExprStmt{
+					X: &ast.CallExpr{
+						Fun: ast.NewIdent("panic"),
+						Args: []ast.Expr{
+							&ast.BasicLit{
+								Value: `"unimplemented"`,
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	b := bytes.NewBufferString("\n\n")
+	if err := format.Node(b, fset, decl); err != nil {
+		return nil, err
+	}
+	return &analysis.SuggestedFix{
+		Message: fmt.Sprintf("Create function \"%s\"", ident.Name),
+		TextEdits: []analysis.TextEdit{{
+			Pos:     pos,
+			End:     pos,
+			NewText: b.Bytes(),
+		}},
+	}, nil
+}
+func typeToArgName(ty types.Type) string {
+	s := types.Default(ty).String()
+
+	switch t := ty.(type) {
+	case *types.Basic:
+		// use first letter in type name for basic types
+		return s[0:1]
+	case *types.Slice:
+		// use element type to decide var name for slices
+		return typeToArgName(t.Elem())
+	case *types.Array:
+		// use element type to decide var name for arrays
+		return typeToArgName(t.Elem())
+	case *types.Chan:
+		return "ch"
+	}
+
+	s = strings.TrimFunc(s, func(r rune) bool {
+		return !unicode.IsLetter(r)
+	})
+
+	if s == "error" {
+		return "err"
+	}
+
+	// remove package (if present)
+	// and make first letter lowercase
+	a := []rune(s[strings.LastIndexByte(s, '.')+1:])
+	a[0] = unicode.ToLower(a[0])
+	return string(a)
+}
diff --git a/internal/lsp/cmd/test/suggested_fix.go b/internal/lsp/cmd/test/suggested_fix.go
index 160dcdf..c819e05 100644
--- a/internal/lsp/cmd/test/suggested_fix.go
+++ b/internal/lsp/cmd/test/suggested_fix.go
@@ -24,7 +24,7 @@
 	args = append(args, actionKinds...)
 	got, stderr := r.NormalizeGoplsCmd(t, args...)
 	if stderr == "ExecuteCommand is not yet supported on the command line" {
-		t.Skipf(stderr)
+		return // don't skip to keep the summary counts correct
 	}
 	want := string(r.data.Golden("suggestedfix_"+tests.SpanName(spn), filename, func() ([]byte, error) {
 		return []byte(got), nil
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index b78b57d..59eb437 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -562,11 +562,6 @@
 							Default: "true",
 						},
 						{
-							Name:    "\"implementmissing\"",
-							Doc:     "suggested fixes for \"undeclared name: %s\" on a function call\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: %s\" that happen for a function call. For example:\n\tfunc m() {\n\t  a(1)\n\t}\nwill turn into\n\tfunc m() {\n\t  a(1)\n\t}\n\n\tfunc a(i int) {}\n",
-							Default: "false",
-						},
-						{
 							Name:    "\"nonewvars\"",
 							Doc:     "suggested fixes for \"no new vars on left side of :=\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no new vars on left side of :=\". For example:\n\tz := 1\n\tz := 2\nwill turn into\n\tz := 1\n\tz = 2\n",
 							Default: "true",
@@ -578,7 +573,7 @@
 						},
 						{
 							Name:    "\"undeclaredname\"",
-							Doc:     "suggested fixes for \"undeclared name: <>\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: <>\". It will insert a new statement:\n\"<> := \".",
+							Doc:     "suggested fixes for \"undeclared name: <>\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: <>\". It will either insert a new statement,\nsuch as:\n\n\"<> := \"\n\nor a new function declaration, such as:\n\nfunc <>(inferred parameters) {\n\tpanic(\"implement me!\")\n}\n",
 							Default: "true",
 						},
 						{
@@ -1140,11 +1135,6 @@
 			Default: true,
 		},
 		{
-			Name:    "implementmissing",
-			Doc:     "suggested fixes for \"undeclared name: %s\" on a function call\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: %s\" that happen for a function call. For example:\n\tfunc m() {\n\t  a(1)\n\t}\nwill turn into\n\tfunc m() {\n\t  a(1)\n\t}\n\n\tfunc a(i int) {}\n",
-			Default: false,
-		},
-		{
 			Name:    "nonewvars",
 			Doc:     "suggested fixes for \"no new vars on left side of :=\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no new vars on left side of :=\". For example:\n\tz := 1\n\tz := 2\nwill turn into\n\tz := 1\n\tz = 2\n",
 			Default: true,
@@ -1156,7 +1146,7 @@
 		},
 		{
 			Name:    "undeclaredname",
-			Doc:     "suggested fixes for \"undeclared name: <>\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: <>\". It will insert a new statement:\n\"<> := \".",
+			Doc:     "suggested fixes for \"undeclared name: <>\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: <>\". It will either insert a new statement,\nsuch as:\n\n\"<> := \"\n\nor a new function declaration, such as:\n\nfunc <>(inferred parameters) {\n\tpanic(\"implement me!\")\n}\n",
 			Default: true,
 		},
 		{
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 1efe177..374fb70 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -49,7 +49,6 @@
 	"golang.org/x/tools/go/analysis/passes/unusedwrite"
 	"golang.org/x/tools/internal/lsp/analysis/fillreturns"
 	"golang.org/x/tools/internal/lsp/analysis/fillstruct"
-	"golang.org/x/tools/internal/lsp/analysis/implementmissing"
 	"golang.org/x/tools/internal/lsp/analysis/nonewvars"
 	"golang.org/x/tools/internal/lsp/analysis/noresultvalues"
 	"golang.org/x/tools/internal/lsp/analysis/simplifycompositelit"
@@ -756,9 +755,6 @@
 	if _, ok := o.Analyses[unusedparams.Analyzer.Name]; !ok {
 		o.Analyses[unusedparams.Analyzer.Name] = true
 	}
-	if _, ok := o.Analyses[implementmissing.Analyzer.Name]; !ok {
-		o.Analyses[implementmissing.Analyzer.Name] = true
-	}
 }
 
 func (o *Options) set(name string, value interface{}, seen map[string]struct{}) OptionResult {
@@ -1176,11 +1172,6 @@
 			ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
 			Enabled:    true,
 		},
-		implementmissing.Analyzer.Name: {
-			Analyzer:   implementmissing.Analyzer,
-			ActionKind: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix},
-			Enabled:    false,
-		},
 		nonewvars.Analyzer.Name: {
 			Analyzer: nonewvars.Analyzer,
 			Enabled:  true,
diff --git a/internal/lsp/testdata/missingfunction/channels.go b/internal/lsp/testdata/missingfunction/channels.go
new file mode 100644
index 0000000..436491c
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/channels.go
@@ -0,0 +1,9 @@
+package missingfunction
+
+func channels(s string) {
+	undefinedChannels(c()) //@suggestedfix("undefinedChannels", "quickfix")
+}
+
+func c() (<-chan string, chan string) {
+	return make(<-chan string), make(chan string)
+}
diff --git a/internal/lsp/testdata/missingfunction/channels.go.golden b/internal/lsp/testdata/missingfunction/channels.go.golden
new file mode 100644
index 0000000..f5078fe
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/channels.go.golden
@@ -0,0 +1,15 @@
+-- suggestedfix_channels_4_2 --
+package missingfunction
+
+func channels(s string) {
+	undefinedChannels(c()) //@suggestedfix("undefinedChannels", "quickfix")
+}
+
+func undefinedChannels(ch1 <-chan string, ch2 chan string) {
+	panic("unimplemented")
+}
+
+func c() (<-chan string, chan string) {
+	return make(<-chan string), make(chan string)
+}
+
diff --git a/internal/lsp/testdata/missingfunction/consecutive_params.go b/internal/lsp/testdata/missingfunction/consecutive_params.go
new file mode 100644
index 0000000..d2ec3be
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/consecutive_params.go
@@ -0,0 +1,6 @@
+package missingfunction
+
+func consecutiveParams() {
+	var s string
+	undefinedConsecutiveParams(s, s) //@suggestedfix("undefinedConsecutiveParams", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/consecutive_params.go.golden b/internal/lsp/testdata/missingfunction/consecutive_params.go.golden
new file mode 100644
index 0000000..14a7664
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/consecutive_params.go.golden
@@ -0,0 +1,12 @@
+-- suggestedfix_consecutive_params_5_2 --
+package missingfunction
+
+func consecutiveParams() {
+	var s string
+	undefinedConsecutiveParams(s, s) //@suggestedfix("undefinedConsecutiveParams", "quickfix")
+}
+
+func undefinedConsecutiveParams(s1, s2 string) {
+	panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/error_param.go b/internal/lsp/testdata/missingfunction/error_param.go
new file mode 100644
index 0000000..9fd943f
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/error_param.go
@@ -0,0 +1,6 @@
+package missingfunction
+
+func errorParam() {
+	var err error
+	undefinedErrorParam(err) //@suggestedfix("undefinedErrorParam", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/error_param.go.golden b/internal/lsp/testdata/missingfunction/error_param.go.golden
new file mode 100644
index 0000000..2e12711
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/error_param.go.golden
@@ -0,0 +1,12 @@
+-- suggestedfix_error_param_5_2 --
+package missingfunction
+
+func errorParam() {
+	var err error
+	undefinedErrorParam(err) //@suggestedfix("undefinedErrorParam", "quickfix")
+}
+
+func undefinedErrorParam(err error) {
+	panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/literals.go b/internal/lsp/testdata/missingfunction/literals.go
new file mode 100644
index 0000000..e276eae
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/literals.go
@@ -0,0 +1,7 @@
+package missingfunction
+
+type T struct{}
+
+func literals() {
+	undefinedLiterals("hey compiler", T{}, &T{}) //@suggestedfix("undefinedLiterals", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/literals.go.golden b/internal/lsp/testdata/missingfunction/literals.go.golden
new file mode 100644
index 0000000..04782b9
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/literals.go.golden
@@ -0,0 +1,29 @@
+-- suggestedfix_literals_10_2 --
+// 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 missingfunction
+
+type T struct{}
+
+func literals() {
+	undefinedLiterals("hey compiler", T{}, &T{}) //@suggestedfix("undefinedLiterals", "quickfix")
+}
+
+func undefinedLiterals(s string, t1 T, t2 *T) {
+	panic("implement me!")
+}
+-- suggestedfix_literals_6_2 --
+package missingfunction
+
+type T struct{}
+
+func literals() {
+	undefinedLiterals("hey compiler", T{}, &T{}) //@suggestedfix("undefinedLiterals", "quickfix")
+}
+
+func undefinedLiterals(s string, t1 T, t2 *T) {
+	panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/operation.go b/internal/lsp/testdata/missingfunction/operation.go
new file mode 100644
index 0000000..0408219
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/operation.go
@@ -0,0 +1,7 @@
+package missingfunction
+
+import "time"
+
+func operation() {
+	undefinedOperation(10 * time.Second) //@suggestedfix("undefinedOperation", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/operation.go.golden b/internal/lsp/testdata/missingfunction/operation.go.golden
new file mode 100644
index 0000000..5e35f30
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/operation.go.golden
@@ -0,0 +1,29 @@
+-- suggestedfix_operation_10_2 --
+// 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 missingfunction
+
+import "time"
+
+func operation() {
+	undefinedOperation(10 * time.Second) //@suggestedfix("undefinedOperation", "quickfix")
+}
+
+func undefinedOperation(duration time.Duration) {
+	panic("implement me!")
+}
+-- suggestedfix_operation_6_2 --
+package missingfunction
+
+import "time"
+
+func operation() {
+	undefinedOperation(10 * time.Second) //@suggestedfix("undefinedOperation", "quickfix")
+}
+
+func undefinedOperation(duration time.Duration) {
+	panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/selector.go b/internal/lsp/testdata/missingfunction/selector.go
new file mode 100644
index 0000000..afd1ab6
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/selector.go
@@ -0,0 +1,6 @@
+package missingfunction
+
+func selector() {
+	m := map[int]bool{}
+	undefinedSelector(m[1]) //@suggestedfix("undefinedSelector", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/selector.go.golden b/internal/lsp/testdata/missingfunction/selector.go.golden
new file mode 100644
index 0000000..c48691c
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/selector.go.golden
@@ -0,0 +1,12 @@
+-- suggestedfix_selector_5_2 --
+package missingfunction
+
+func selector() {
+	m := map[int]bool{}
+	undefinedSelector(m[1]) //@suggestedfix("undefinedSelector", "quickfix")
+}
+
+func undefinedSelector(b bool) {
+	panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/slice.go b/internal/lsp/testdata/missingfunction/slice.go
new file mode 100644
index 0000000..4a562a2
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/slice.go
@@ -0,0 +1,5 @@
+package missingfunction
+
+func slice() {
+	undefinedSlice([]int{1, 2}) //@suggestedfix("undefinedSlice", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/slice.go.golden b/internal/lsp/testdata/missingfunction/slice.go.golden
new file mode 100644
index 0000000..0ccb861
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/slice.go.golden
@@ -0,0 +1,11 @@
+-- suggestedfix_slice_4_2 --
+package missingfunction
+
+func slice() {
+	undefinedSlice([]int{1, 2}) //@suggestedfix("undefinedSlice", "quickfix")
+}
+
+func undefinedSlice(i []int) {
+	panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/missingfunction/tuple.go b/internal/lsp/testdata/missingfunction/tuple.go
new file mode 100644
index 0000000..1c4782c
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/tuple.go
@@ -0,0 +1,9 @@
+package missingfunction
+
+func tuple() {
+	undefinedTuple(b()) //@suggestedfix("undefinedTuple", "quickfix")
+}
+
+func b() (string, error) {
+	return "", nil
+}
diff --git a/internal/lsp/testdata/missingfunction/tuple.go.golden b/internal/lsp/testdata/missingfunction/tuple.go.golden
new file mode 100644
index 0000000..1e12bb7
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/tuple.go.golden
@@ -0,0 +1,15 @@
+-- suggestedfix_tuple_4_2 --
+package missingfunction
+
+func tuple() {
+	undefinedTuple(b()) //@suggestedfix("undefinedTuple", "quickfix")
+}
+
+func undefinedTuple(s string, err error) {
+	panic("unimplemented")
+}
+
+func b() (string, error) {
+	return "", nil
+}
+
diff --git a/internal/lsp/testdata/missingfunction/unique_params.go b/internal/lsp/testdata/missingfunction/unique_params.go
new file mode 100644
index 0000000..ffaba3f
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/unique_params.go
@@ -0,0 +1,7 @@
+package missingfunction
+
+func uniqueArguments() {
+	var s string
+	var i int
+	undefinedUniqueArguments(s, i, s) //@suggestedfix("undefinedUniqueArguments", "quickfix")
+}
diff --git a/internal/lsp/testdata/missingfunction/unique_params.go.golden b/internal/lsp/testdata/missingfunction/unique_params.go.golden
new file mode 100644
index 0000000..74fb91a
--- /dev/null
+++ b/internal/lsp/testdata/missingfunction/unique_params.go.golden
@@ -0,0 +1,30 @@
+-- suggestedfix_unique_params_10_2 --
+// 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 missingfunction
+
+func uniqueArguments() {
+	var s string
+	var i int
+	undefinedUniqueArguments(s, i, s) //@suggestedfix("undefinedUniqueArguments", "quickfix")
+}
+
+func undefinedUniqueArguments(s1 string, i int, s2 string) {
+	panic("implement me!")
+}
+
+-- suggestedfix_unique_params_6_2 --
+package missingfunction
+
+func uniqueArguments() {
+	var s string
+	var i int
+	undefinedUniqueArguments(s, i, s) //@suggestedfix("undefinedUniqueArguments", "quickfix")
+}
+
+func undefinedUniqueArguments(s1 string, i int, s2 string) {
+	panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden
index 7143365..9e6334a 100644
--- a/internal/lsp/testdata/summary.txt.golden
+++ b/internal/lsp/testdata/summary.txt.golden
@@ -13,7 +13,7 @@
 FormatCount = 6
 ImportCount = 8
 SemanticTokenCount = 3
-SuggestedFixCount = 40
+SuggestedFixCount = 49
 FunctionExtractionCount = 24
 MethodExtractionCount = 6
 DefinitionsCount = 95
diff --git a/internal/lsp/testdata/summary_go1.18.txt.golden b/internal/lsp/testdata/summary_go1.18.txt.golden
index aaeb262..fefd3d2 100644
--- a/internal/lsp/testdata/summary_go1.18.txt.golden
+++ b/internal/lsp/testdata/summary_go1.18.txt.golden
@@ -13,7 +13,7 @@
 FormatCount = 6
 ImportCount = 8
 SemanticTokenCount = 3
-SuggestedFixCount = 40
+SuggestedFixCount = 49
 FunctionExtractionCount = 24
 MethodExtractionCount = 6
 DefinitionsCount = 99