Skip to content

Commit 244a31e

Browse files
xzbdmwgopherbot
authored andcommitted
gopls/internal: CodeAction: quickfix to generate missing method
This change provides a new "quickfix" code action to repair a function call x.f() with a "type X has no field or method f" error by generating a stub declaration of the missing method. - extract common functionality about adjusting import, generating diffs to a shared function. - add new stubMissingCalledFunctionFixer (for missing method calls), change stubMethodsFixer to stubMissingInterfaceMethodsFixer (for interfaces) to make it not vague about which kind of stub it actually be. - algos to get type info: for arguments, get type directly, for return values, currently only implement inferring from variable assignment and parameter assignment. - remove a test in stub.txt that tests local types can't be stubbed, after this CL, this check has been moved to GetCallStubInfo and GetIfaceStubInfo, thus gopls will not offer a bad fix. There is a detailed test that demonstrated this pr. Fixes golang/go#69692 Change-Id: Ia7aecdaf895da2a9a33b706f26b7766e4d42c8a1 GitHub-Last-Rev: 7d9f4e0 GitHub-Pull-Request: #528 Reviewed-on: https://go-review.googlesource.com/c/tools/+/617619 Reviewed-by: Robert Findley <[email protected]> Reviewed-by: Alan Donovan <[email protected]> Auto-Submit: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Commit-Queue: Alan Donovan <[email protected]>
1 parent 87d6131 commit 244a31e

File tree

14 files changed

+903
-248
lines changed

14 files changed

+903
-248
lines changed

gopls/doc/features/diagnostics.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ Client support:
127127
<!-- Below we list any quick fixes (by their internal fix name)
128128
that aren't analyzers. -->
129129

130-
### `stubMethods`: Declare missing methods of type
130+
### `stubMissingInterfaceMethods`: Declare missing methods of I
131131

132132
When a value of a concrete type is assigned to a variable of an
133133
interface type, but the concrete type does not possess all the
@@ -169,6 +169,34 @@ position.
169169
client there, or a progress notification indicating that something
170170
happened.)
171171

172+
### `StubMissingCalledFunction`: Declare missing method T.f
173+
174+
When you attempt to call a method on a type that does not have that method,
175+
the compiler will report an error such as "type X has no field or method Y".
176+
In this scenario, gopls now offers a quick fix to generate a stub declaration of
177+
the missing method, inferring its type from the call.
178+
179+
Consider the following code where `Foo` does not have a method `bar`:
180+
181+
```go
182+
type Foo struct{}
183+
184+
func main() {
185+
var s string
186+
f := Foo{}
187+
s = f.bar("str", 42) // error: f.bar undefined (type Foo has no field or method bar)
188+
}
189+
```
190+
191+
Gopls will offer a quick fix, "Declare missing method Foo.bar".
192+
When invoked, it creates the following declaration:
193+
194+
```go
195+
func (f Foo) bar(s string, i int) string {
196+
panic("unimplemented")
197+
}
198+
```
199+
172200
<!--
173201
174202
dorky details and deletia:

gopls/doc/release/v0.17.0.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,12 @@ function's Go `func` declaration. If the function is implemented in C
6363
or assembly, the function has no body. Executing a second Definition
6464
query (while already at the Go declaration) will navigate you to the
6565
assembly implementation.
66+
67+
## Generate missing method from function call
68+
69+
When you attempt to call a method on a type that does not have that method,
70+
the compiler will report an error like “type X has no field or method Y”.
71+
Gopls now offers a new code action, “Declare missing method of T.f”,
72+
where T is the concrete type and f is the undefined method.
73+
The stub method's signature is inferred
74+
from the context of the call.

gopls/internal/golang/codeaction.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -301,20 +301,32 @@ func quickFix(ctx context.Context, req *codeActionsRequest) error {
301301
continue
302302
}
303303

304+
msg := typeError.Error()
305+
switch {
304306
// "Missing method" error? (stubmethods)
305307
// Offer a "Declare missing methods of INTERFACE" code action.
306-
// See [stubMethodsFixer] for command implementation.
307-
msg := typeError.Error()
308-
if strings.Contains(msg, "missing method") ||
309-
strings.HasPrefix(msg, "cannot convert") ||
310-
strings.Contains(msg, "not implement") {
308+
// See [stubMissingInterfaceMethodsFixer] for command implementation.
309+
case strings.Contains(msg, "missing method"),
310+
strings.HasPrefix(msg, "cannot convert"),
311+
strings.Contains(msg, "not implement"):
311312
path, _ := astutil.PathEnclosingInterval(req.pgf.File, start, end)
312-
si := stubmethods.GetStubInfo(req.pkg.FileSet(), info, path, start)
313+
si := stubmethods.GetIfaceStubInfo(req.pkg.FileSet(), info, path, start)
313314
if si != nil {
314315
qf := typesutil.FileQualifier(req.pgf.File, si.Concrete.Obj().Pkg(), info)
315316
iface := types.TypeString(si.Interface.Type(), qf)
316317
msg := fmt.Sprintf("Declare missing methods of %s", iface)
317-
req.addApplyFixAction(msg, fixStubMethods, req.loc)
318+
req.addApplyFixAction(msg, fixMissingInterfaceMethods, req.loc)
319+
}
320+
321+
// "type X has no field or method Y" compiler error.
322+
// Offer a "Declare missing method T.f" code action.
323+
// See [stubMissingCalledFunctionFixer] for command implementation.
324+
case strings.Contains(msg, "has no field or method"):
325+
path, _ := astutil.PathEnclosingInterval(req.pgf.File, start, end)
326+
si := stubmethods.GetCallStubInfo(req.pkg.FileSet(), info, path, start)
327+
if si != nil {
328+
msg := fmt.Sprintf("Declare missing method %s.%s", si.Receiver.Obj().Name(), si.MethodName)
329+
req.addApplyFixAction(msg, fixMissingCalledFunction, req.loc)
318330
}
319331
}
320332
}

gopls/internal/golang/completion/format.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"golang.org/x/tools/gopls/internal/golang/completion/snippet"
1818
"golang.org/x/tools/gopls/internal/protocol"
1919
"golang.org/x/tools/gopls/internal/util/safetoken"
20+
"golang.org/x/tools/gopls/internal/util/typesutil"
2021
"golang.org/x/tools/internal/event"
2122
"golang.org/x/tools/internal/imports"
2223
"golang.org/x/tools/internal/typesinternal"
@@ -62,7 +63,7 @@ func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, e
6263
if isTypeName(obj) && c.wantTypeParams() {
6364
// obj is a *types.TypeName, so its type must be Alias|Named.
6465
tparams := typesinternal.TypeParams(obj.Type().(typesinternal.NamedOrAlias))
65-
label += golang.FormatTypeParams(tparams)
66+
label += typesutil.FormatTypeParams(tparams)
6667
insert = label // maintain invariant above (label == insert)
6768
}
6869

gopls/internal/golang/fix.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,15 @@ func singleFile(fixer1 singleFileFixer) fixer {
5858

5959
// Names of ApplyFix.Fix created directly by the CodeAction handler.
6060
const (
61-
fixExtractVariable = "extract_variable"
62-
fixExtractFunction = "extract_function"
63-
fixExtractMethod = "extract_method"
64-
fixInlineCall = "inline_call"
65-
fixInvertIfCondition = "invert_if_condition"
66-
fixSplitLines = "split_lines"
67-
fixJoinLines = "join_lines"
68-
fixStubMethods = "stub_methods"
61+
fixExtractVariable = "extract_variable"
62+
fixExtractFunction = "extract_function"
63+
fixExtractMethod = "extract_method"
64+
fixInlineCall = "inline_call"
65+
fixInvertIfCondition = "invert_if_condition"
66+
fixSplitLines = "split_lines"
67+
fixJoinLines = "join_lines"
68+
fixMissingInterfaceMethods = "stub_missing_interface_method"
69+
fixMissingCalledFunction = "stub_missing_called_function"
6970
)
7071

7172
// ApplyFix applies the specified kind of suggested fix to the given
@@ -102,14 +103,15 @@ func ApplyFix(ctx context.Context, fix string, snapshot *cache.Snapshot, fh file
102103

103104
// Ad-hoc fixers: these are used when the command is
104105
// constructed directly by logic in server/code_action.
105-
fixExtractFunction: singleFile(extractFunction),
106-
fixExtractMethod: singleFile(extractMethod),
107-
fixExtractVariable: singleFile(extractVariable),
108-
fixInlineCall: inlineCall,
109-
fixInvertIfCondition: singleFile(invertIfCondition),
110-
fixSplitLines: singleFile(splitLines),
111-
fixJoinLines: singleFile(joinLines),
112-
fixStubMethods: stubMethodsFixer,
106+
fixExtractFunction: singleFile(extractFunction),
107+
fixExtractMethod: singleFile(extractMethod),
108+
fixExtractVariable: singleFile(extractVariable),
109+
fixInlineCall: inlineCall,
110+
fixInvertIfCondition: singleFile(invertIfCondition),
111+
fixSplitLines: singleFile(splitLines),
112+
fixJoinLines: singleFile(joinLines),
113+
fixMissingInterfaceMethods: stubMissingInterfaceMethodsFixer,
114+
fixMissingCalledFunction: stubMissingCalledFunctionFixer,
113115
}
114116
fixer, ok := fixers[fix]
115117
if !ok {

0 commit comments

Comments
 (0)