Skip to content

Commit

Permalink
resolve suggestions
Browse files Browse the repository at this point in the history
- postpone param and return type caculation to Emit
- add marker test
- remove unnecessary test in stub.txt that test local type can not be
stubbed
  • Loading branch information
xzbdmw committed Oct 11, 2024
1 parent cac52c7 commit 2a9e003
Show file tree
Hide file tree
Showing 10 changed files with 438 additions and 363 deletions.
4 changes: 2 additions & 2 deletions gopls/doc/features/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,12 @@ func (NegativeErr) Error() string {
}
```

### `StubMissingCalledFunction`: Generate missing methods from function calls
### `StubMissingCalledFunction`: Generate missing method from function calls

When you attempt to call a method on a type that does not have that method,
the compiler will report an error like “type X has no field or method Y”.
In this scenario, gopls now offers a quick fix to generate a stub declaration of
the missing method on that concrete type. The correct signature is inferred
the missing method on that concrete type. The the stub method's signature is inferred
from the method call.

Consider the following code where `Foo` does not have a method `bar`:
Expand Down
8 changes: 4 additions & 4 deletions gopls/doc/release/v0.17.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ or assembly, the function has no body. Executing a second Definition
query (while already at the Go declaration) will navigate you to the
assembly implementation.

## Generate missing method from function calls
Gopls now offers a new code action, “Declare missing methods of Type T”,
## Generate missing method from function call
When you attempt to call a method on a type that does not have that method,
the compiler will report an error like “type X has no field or method Y”.
In this scenario, gopls now offers a quick fix to generate a stub declaration of
the missing method on that concrete type, the correct signature is inferred
Gopls now offers a new code action, “Declare missing method of T.f”,
where T is the concrete type and f is the undefined method.
In this scenario, the stub method's signature is inferred
from the method call.
14 changes: 7 additions & 7 deletions gopls/internal/golang/codeaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,9 @@ func quickFix(ctx context.Context, req *codeActionsRequest) error {
switch {
// "Missing method" error? (stubmethods)
// Offer a "Declare missing methods of INTERFACE" code action.
// See [StubMissingInterfaceMethodsFixer] for command implementation.
case strings.Contains(msg, "missing method") ||
strings.HasPrefix(msg, "cannot convert") ||
// See [stubMissingInterfaceMethodsFixer] for command implementation.
case strings.Contains(msg, "missing method"),
strings.HasPrefix(msg, "cannot convert"),
strings.Contains(msg, "not implement"):
path, _ := astutil.PathEnclosingInterval(req.pgf.File, start, end)
si := stubmethods.GetIfaceStubInfo(req.pkg.FileSet(), info, path, start)
Expand All @@ -317,14 +317,14 @@ func quickFix(ctx context.Context, req *codeActionsRequest) error {
msg := fmt.Sprintf("Declare missing methods of %s", iface)
req.addApplyFixAction(msg, fixMissingInterfaceMethods, req.loc)
}
// type X has no field or method Y” complier error.
// Offer a "Declare missing methods of X" code action.
// See [StubMissingCalledFunctionFixer] for command implementation.
// "type X has no field or method Y" compiler error.
// Offer a "Declare missing method of T.f" code action.
// See [stubMissingCalledFunctionFixer] for command implementation.
case strings.Contains(msg, "has no field or method"):
path, _ := astutil.PathEnclosingInterval(req.pgf.File, start, end)
si := stubmethods.GetCallStubInfo(req.pkg.FileSet(), info, path, start)
if si != nil {
msg := fmt.Sprintf("Declare missing methods of %s", si.Receiver.Obj().Name())
msg := fmt.Sprintf("Declare missing method of %s.%s", si.Receiver.Obj().Name(), si.MethodName)
req.addApplyFixAction(msg, fixMissingCalledFunction, req.loc)
}
}
Expand Down
4 changes: 2 additions & 2 deletions gopls/internal/golang/fix.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ func ApplyFix(ctx context.Context, fix string, snapshot *cache.Snapshot, fh file
fixInvertIfCondition: singleFile(invertIfCondition),
fixSplitLines: singleFile(splitLines),
fixJoinLines: singleFile(joinLines),
fixMissingInterfaceMethods: StubMissingInterfaceMethodsFixer,
fixMissingCalledFunction: StubMissingCalledFunctionFixer,
fixMissingInterfaceMethods: stubMissingInterfaceMethodsFixer,
fixMissingCalledFunction: stubMissingCalledFunctionFixer,
}
fixer, ok := fixers[fix]
if !ok {
Expand Down
40 changes: 18 additions & 22 deletions gopls/internal/golang/stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"go/parser"
"go/token"
"go/types"
"io"
pathpkg "path"
"strings"

Expand All @@ -29,15 +28,10 @@ import (
"golang.org/x/tools/internal/typesinternal"
)

// An emitter writes new top-level declarations into an existing
// file. References to symbols should be qualified using qual, which
// respects the local import environment.
type emitter = func(out *bytes.Buffer, qual types.Qualifier) error

// StubMissingInterfaceMethodsFixer returns a suggested fix to declare the missing
// stubMissingInterfaceMethodsFixer returns a suggested fix to declare the missing
// methods of the concrete type that is assigned to an interface type
// at the cursor position.
func StubMissingInterfaceMethodsFixer(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) {
func stubMissingInterfaceMethodsFixer(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) {
nodes, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
si := stubmethods.GetIfaceStubInfo(pkg.FileSet(), pkg.TypesInfo(), nodes, start)
if si == nil {
Expand All @@ -46,10 +40,10 @@ func StubMissingInterfaceMethodsFixer(ctx context.Context, snapshot *cache.Snaps
return insertDeclsAfter(ctx, snapshot, pkg.Metadata(), si.Fset, si.Concrete.Obj(), si.Emit)
}

// StubMissingCalledFunctionFixer returns a suggested fix to declare the missing
// methods that the user may want to generate based on CallExpr
// stubMissingCalledFunctionFixer returns a suggested fix to declare the missing
// method that the user may want to generate based on CallExpr
// at the cursor position.
func StubMissingCalledFunctionFixer(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) {
func stubMissingCalledFunctionFixer(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) {
nodes, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
si := stubmethods.GetCallStubInfo(pkg.FileSet(), pkg.TypesInfo(), nodes, start)
if si == nil {
Expand All @@ -58,6 +52,11 @@ func StubMissingCalledFunctionFixer(ctx context.Context, snapshot *cache.Snapsho
return insertDeclsAfter(ctx, snapshot, pkg.Metadata(), si.Fset, si.Receiver.Obj(), si.Emit)
}

// An emitter writes new top-level declarations into an existing
// file. References to symbols should be qualified using qual, which
// respects the local import environment.
type emitter = func(out *bytes.Buffer, qual types.Qualifier) error

// insertDeclsAfter locates the file that declares symbol sym,
// (which must be among the dependencies of mp),
// calls the emit function to generate new declarations,
Expand Down Expand Up @@ -155,19 +154,13 @@ func insertDeclsAfter(ctx context.Context, snapshot *cache.Snapshot, mp *metadat
return name
}

var newBuf bytes.Buffer
err = emit(&newBuf, qual)
if err != nil {
return nil, nil, err
}

// Compute insertion point for new methods:
// Compute insertion point for new declarations:
// after the top-level declaration enclosing the (package-level) type.
insertOffset, err := safetoken.Offset(declPGF.Tok, declPGF.File.End())
if err != nil {
return nil, nil, bug.Errorf("internal error: end position outside file bounds: %v", err)
}
concOffset, err := safetoken.Offset(fset.File(sym.Pos()), sym.Pos())
symOffset, err := safetoken.Offset(fset.File(sym.Pos()), sym.Pos())
if err != nil {
return nil, nil, bug.Errorf("internal error: finding type decl offset: %v", err)
}
Expand All @@ -176,18 +169,21 @@ func insertDeclsAfter(ctx context.Context, snapshot *cache.Snapshot, mp *metadat
if err != nil {
return nil, nil, bug.Errorf("internal error: finding decl offset: %v", err)
}
if declEndOffset > concOffset {
if declEndOffset > symOffset {
insertOffset = declEndOffset
break
}
}

// Splice the new methods into the file content.
// Splice the new declarations into the file content.
var buf bytes.Buffer
input := declPGF.Mapper.Content // unfixed content of file
buf.Write(input[:insertOffset])
buf.WriteByte('\n')
io.Copy(&buf, &newBuf)
err = emit(&buf, qual)
if err != nil {
return nil, nil, err
}
buf.Write(input[insertOffset:])

// Re-parse the file.
Expand Down
Loading

0 comments on commit 2a9e003

Please sign in to comment.