internal/lsp/analysis/stubmethods: recognize *ast.CallExpr

This change makes it so that the stub methods analysis can recognize
errors happening to method and function call expressions that are being
passed a concrete type to an interface parameter. This way, a method stub CodeAction will appear at the call site.

Updates golang/go#37537

Change-Id: I886d53f06a85b9e5160d882aa742bb2b7fcea139
Reviewed-on: https://go-review.googlesource.com/c/tools/+/404655
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Suzy Mueller <suzmue@golang.org>
Run-TryBot: Suzy Mueller <suzmue@golang.org>
diff --git a/internal/lsp/analysis/stubmethods/stubmethods.go b/internal/lsp/analysis/stubmethods/stubmethods.go
index c2a4138..f9dc69a 100644
--- a/internal/lsp/analysis/stubmethods/stubmethods.go
+++ b/internal/lsp/analysis/stubmethods/stubmethods.go
@@ -105,11 +105,59 @@
 			return si
 		case *ast.AssignStmt:
 			return fromAssignStmt(ti, n, pos)
+		case *ast.CallExpr:
+			// Note that some call expressions don't carry the interface type
+			// because they don't point to a function or method declaration elsewhere.
+			// For eaxmple, "var Interface = (*Concrete)(nil)". In that case, continue
+			// this loop to encounter other possibilities such as *ast.ValueSpec or others.
+			si := fromCallExpr(ti, pos, n)
+			if si != nil {
+				return si
+			}
 		}
 	}
 	return nil
 }
 
+// fromCallExpr tries to find an *ast.CallExpr's function declaration and
+// analyzes a function call's signature against the passed in parameter to deduce
+// the concrete and interface types.
+func fromCallExpr(ti *types.Info, pos token.Pos, ce *ast.CallExpr) *StubInfo {
+	paramIdx := -1
+	for i, p := range ce.Args {
+		if pos >= p.Pos() && pos <= p.End() {
+			paramIdx = i
+			break
+		}
+	}
+	if paramIdx == -1 {
+		return nil
+	}
+	p := ce.Args[paramIdx]
+	concObj, pointer := concreteType(p, ti)
+	if concObj == nil || concObj.Obj().Pkg() == nil {
+		return nil
+	}
+	tv, ok := ti.Types[ce.Fun]
+	if !ok {
+		return nil
+	}
+	sig, ok := tv.Type.(*types.Signature)
+	if !ok {
+		return nil
+	}
+	sigVar := sig.Params().At(paramIdx)
+	iface := ifaceObjFromType(sigVar.Type())
+	if iface == nil {
+		return nil
+	}
+	return &StubInfo{
+		Concrete:  concObj,
+		Pointer:   pointer,
+		Interface: iface,
+	}
+}
+
 // fromReturnStmt analyzes a "return" statement to extract
 // a concrete type that is trying to be returned as an interface type.
 //
@@ -290,8 +338,11 @@
 	if !ok {
 		return nil
 	}
-	typ := tv.Type
-	named, ok := typ.(*types.Named)
+	return ifaceObjFromType(tv.Type)
+}
+
+func ifaceObjFromType(t types.Type) types.Object {
+	named, ok := t.(*types.Named)
 	if !ok {
 		return nil
 	}
diff --git a/internal/lsp/testdata/stub/stub_call_expr.go b/internal/lsp/testdata/stub/stub_call_expr.go
new file mode 100644
index 0000000..775b0e5
--- /dev/null
+++ b/internal/lsp/testdata/stub/stub_call_expr.go
@@ -0,0 +1,13 @@
+package stub
+
+func main() {
+	check(&callExpr{}) //@suggestedfix("&", "refactor.rewrite")
+}
+
+func check(err error) {
+	if err != nil {
+		panic(err)
+	}
+}
+
+type callExpr struct{}
diff --git a/internal/lsp/testdata/stub/stub_call_expr.go.golden b/internal/lsp/testdata/stub/stub_call_expr.go.golden
new file mode 100644
index 0000000..2d12f86
--- /dev/null
+++ b/internal/lsp/testdata/stub/stub_call_expr.go.golden
@@ -0,0 +1,20 @@
+-- suggestedfix_stub_call_expr_4_8 --
+package stub
+
+func main() {
+	check(&callExpr{}) //@suggestedfix("&", "refactor.rewrite")
+}
+
+func check(err error) {
+	if err != nil {
+		panic(err)
+	}
+}
+
+type callExpr struct{}
+
+// Error implements error
+func (*callExpr) Error() string {
+	panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden
index 89f9579..9e1d84d 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 = 62
+SuggestedFixCount = 63
 FunctionExtractionCount = 25
 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 1b4891e..1c6ad92 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 = 63
+SuggestedFixCount = 64
 FunctionExtractionCount = 25
 MethodExtractionCount = 6
 DefinitionsCount = 108