From 13999c26eddb319f8d520c755065aca62e9ac27e Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 29 Jan 2025 15:56:16 +0100 Subject: [PATCH] Move GetFieldStubInfo to stubmethods package --- gopls/internal/golang/codeaction.go | 73 +------------------ gopls/internal/golang/stub.go | 12 ++- .../golang/stubmethods/stubmethods.go | 72 ++++++++++++++++++ gopls/internal/util/typesutil/typesutil.go | 4 +- 4 files changed, 80 insertions(+), 81 deletions(-) diff --git a/gopls/internal/golang/codeaction.go b/gopls/internal/golang/codeaction.go index 5865f9d2ff2..4af47272d74 100644 --- a/gopls/internal/golang/codeaction.go +++ b/gopls/internal/golang/codeaction.go @@ -5,7 +5,6 @@ package golang import ( - "bytes" "context" "encoding/json" "fmt" @@ -42,7 +41,6 @@ import ( // // See ../protocol/codeactionkind.go for some code action theory. func CodeActions(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range, diagnostics []protocol.Diagnostic, enabled func(protocol.CodeActionKind) bool, trigger protocol.CodeActionTriggerKind) (actions []protocol.CodeAction, _ error) { - loc := protocol.Location{URI: fh.URI(), Range: rng} pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full) @@ -341,7 +339,7 @@ func quickFix(ctx context.Context, req *codeActionsRequest) error { } else { // Offer a "Declare missing field T.f" code action. // See [stubMissingStructFieldFixer] for command implementation. - fi := GetFieldStubInfo(req.pkg.FileSet(), info, path) + fi := stubmethods.GetFieldStubInfo(req.pkg.FileSet(), info, path) if fi != nil { msg := fmt.Sprintf("Declare missing struct field %s.%s", fi.Named.Obj().Name(), fi.Expr.Sel.Name) req.addApplyFixAction(msg, fixMissingStructField, req.loc) @@ -364,75 +362,6 @@ func quickFix(ctx context.Context, req *codeActionsRequest) error { return nil } -func GetFieldStubInfo(fset *token.FileSet, info *types.Info, path []ast.Node) *StructFieldInfo { - for _, node := range path { - n, ok := node.(*ast.SelectorExpr) - if !ok { - continue - } - tv, ok := info.Types[n.X] - if !ok { - break - } - - named, ok := tv.Type.(*types.Named) - if !ok { - break - } - - structType, ok := named.Underlying().(*types.Struct) - if !ok { - break - } - - return &StructFieldInfo{ - Fset: fset, - Expr: n, - Struct: structType, - Named: named, - Info: info, - Path: path, - } - } - - return nil -} - -type StructFieldInfo struct { - Fset *token.FileSet - Expr *ast.SelectorExpr - Struct *types.Struct - Named *types.Named - Info *types.Info - Path []ast.Node -} - -// Emit writes to out the missing field based on type info. -func (si *StructFieldInfo) Emit(out *bytes.Buffer, qual types.Qualifier) error { - if si.Expr == nil || si.Expr.Sel == nil { - return fmt.Errorf("invalid selector expression") - } - - // Get types from context at the selector expression position - typesFromContext := typesutil.TypesFromContext(si.Info, si.Path, si.Expr.Pos()) - - // Default to interface{} if we couldn't determine the type from context - var fieldType types.Type - if len(typesFromContext) > 0 && typesFromContext[0] != nil { - fieldType = typesFromContext[0] - } else { - // Create a new interface{} type - fieldType = types.NewInterfaceType(nil, nil) - } - - tpl := "\n\t%s %s" - if si.Struct.NumFields() == 0 { - tpl += "\n" - } - fmt.Fprintf(out, tpl, si.Expr.Sel.Name, types.TypeString(fieldType, qual)) - return nil -} - // allImportsFixesResult is the result of a lazy call to allImportsFixes. // It implements the codeActionsRequest lazyInit interface. type allImportsFixesResult struct { diff --git a/gopls/internal/golang/stub.go b/gopls/internal/golang/stub.go index e77afc16e5b..70a7162a2a6 100644 --- a/gopls/internal/golang/stub.go +++ b/gopls/internal/golang/stub.go @@ -57,7 +57,7 @@ func stubMissingCalledFunctionFixer(ctx context.Context, snapshot *cache.Snapsho // at the cursor position. func stubMissingStructFieldFixer(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) - fi := GetFieldStubInfo(pkg.FileSet(), pkg.TypesInfo(), nodes) + fi := stubmethods.GetFieldStubInfo(pkg.FileSet(), pkg.TypesInfo(), nodes) if fi == nil { return nil, nil, fmt.Errorf("invalid type request") } @@ -252,7 +252,7 @@ func trimVersionSuffix(path string) string { return path } -func insertStructField(ctx context.Context, snapshot *cache.Snapshot, meta *metadata.Package, fieldInfo *StructFieldInfo) (*token.FileSet, *analysis.SuggestedFix, error) { +func insertStructField(ctx context.Context, snapshot *cache.Snapshot, meta *metadata.Package, fieldInfo *stubmethods.StructFieldInfo) (*token.FileSet, *analysis.SuggestedFix, error) { if fieldInfo == nil { return nil, nil, fmt.Errorf("no field info provided") } @@ -304,13 +304,11 @@ func insertStructField(ctx context.Context, snapshot *cache.Snapshot, meta *meta textEdit := analysis.TextEdit{ Pos: insertPos, End: insertPos, - NewText: []byte(buf.String()), + NewText: buf.Bytes(), } - fix := &analysis.SuggestedFix{ + return fieldInfo.Fset, &analysis.SuggestedFix{ Message: fmt.Sprintf("Add field %s to struct %s", fieldInfo.Expr.Sel.Name, fieldInfo.Named.Obj().Name()), TextEdits: []analysis.TextEdit{textEdit}, - } - - return fieldInfo.Fset, fix, nil + }, nil } diff --git a/gopls/internal/golang/stubmethods/stubmethods.go b/gopls/internal/golang/stubmethods/stubmethods.go index f380f5b984d..30e9ce7203e 100644 --- a/gopls/internal/golang/stubmethods/stubmethods.go +++ b/gopls/internal/golang/stubmethods/stubmethods.go @@ -450,3 +450,75 @@ func concreteType(e ast.Expr, info *types.Info) (*types.Named, bool) { } return named, isPtr } + +func GetFieldStubInfo(fset *token.FileSet, info *types.Info, path []ast.Node) *StructFieldInfo { + for _, node := range path { + n, ok := node.(*ast.SelectorExpr) + if !ok { + continue + } + tv, ok := info.Types[n.X] + if !ok { + break + } + + named, ok := tv.Type.(*types.Named) + if !ok { + break + } + + structType, ok := named.Underlying().(*types.Struct) + if !ok { + break + } + + return &StructFieldInfo{ + Fset: fset, + Expr: n, + Struct: structType, + Named: named, + Info: info, + Path: path, + } + } + + return nil +} + +// StructFieldInfo describes f field in x.f where x has a named struct type +type StructFieldInfo struct { + Fset *token.FileSet + Expr *ast.SelectorExpr + Struct *types.Struct + Named *types.Named + Info *types.Info + Path []ast.Node +} + +// Emit writes to out the missing field based on type info. +func (si *StructFieldInfo) Emit(out *bytes.Buffer, qual types.Qualifier) error { + if si.Expr == nil || si.Expr.Sel == nil { + return fmt.Errorf("invalid selector expression") + } + + // Get types from context at the selector expression position + typesFromContext := typesutil.TypesFromContext(si.Info, si.Path, si.Expr.Pos()) + + // Default to interface{} if we couldn't determine the type from context + var fieldType types.Type + if len(typesFromContext) > 0 { + fieldType = typesFromContext[0] + } else { + // Create a new interface{} type + fieldType = types.Universe.Lookup("any").Type() + } + + out.Write([]byte{'\n', '\t'}) + out.WriteString(si.Expr.Sel.Name) + out.WriteByte(' ') + out.WriteString(types.TypeString(fieldType, qual)) + if si.Struct.NumFields() == 0 { + out.WriteByte('\n') + } + return nil +} diff --git a/gopls/internal/util/typesutil/typesutil.go b/gopls/internal/util/typesutil/typesutil.go index 9ace3c8a035..3b11c30156e 100644 --- a/gopls/internal/util/typesutil/typesutil.go +++ b/gopls/internal/util/typesutil/typesutil.go @@ -186,9 +186,9 @@ func TypesFromContext(info *types.Info, path []ast.Node, pos token.Pos) []types. } case *ast.SelectorExpr: for _, n := range path { - assignExpr, ok := n.(*ast.AssignStmt) + assign, ok := n.(*ast.AssignStmt) if ok { - for _, rh := range assignExpr.Rhs { + for _, rh := range assign.Rhs { // basic types basicLit, ok := rh.(*ast.BasicLit) if ok {