golang.org/x/tools/gopls@v0.15.3/internal/analysis/stubmethods/stubmethods.go (about)

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package stubmethods
     6  
     7  import (
     8  	"bytes"
     9  	_ "embed"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/format"
    13  	"go/token"
    14  	"go/types"
    15  	"strings"
    16  
    17  	"golang.org/x/tools/go/analysis"
    18  	"golang.org/x/tools/go/ast/astutil"
    19  	"golang.org/x/tools/gopls/internal/util/typesutil"
    20  	"golang.org/x/tools/internal/analysisinternal"
    21  	"golang.org/x/tools/internal/typesinternal"
    22  )
    23  
    24  //go:embed doc.go
    25  var doc string
    26  
    27  var Analyzer = &analysis.Analyzer{
    28  	Name:             "stubmethods",
    29  	Doc:              analysisinternal.MustExtractDoc(doc, "stubmethods"),
    30  	Run:              run,
    31  	RunDespiteErrors: true,
    32  	URL:              "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stubmethods",
    33  }
    34  
    35  // TODO(rfindley): remove this thin wrapper around the stubmethods refactoring,
    36  // and eliminate the stubmethods analyzer.
    37  //
    38  // Previous iterations used the analysis framework for computing refactorings,
    39  // which proved inefficient.
    40  func run(pass *analysis.Pass) (interface{}, error) {
    41  	for _, err := range pass.TypeErrors {
    42  		var file *ast.File
    43  		for _, f := range pass.Files {
    44  			if f.Pos() <= err.Pos && err.Pos < f.End() {
    45  				file = f
    46  				break
    47  			}
    48  		}
    49  		// Get the end position of the error.
    50  		_, _, end, ok := typesinternal.ReadGo116ErrorData(err)
    51  		if !ok {
    52  			var buf bytes.Buffer
    53  			if err := format.Node(&buf, pass.Fset, file); err != nil {
    54  				continue
    55  			}
    56  			end = analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos)
    57  		}
    58  		if diag, ok := DiagnosticForError(pass.Fset, file, err.Pos, end, err.Msg, pass.TypesInfo); ok {
    59  			pass.Report(diag)
    60  		}
    61  	}
    62  
    63  	return nil, nil
    64  }
    65  
    66  // MatchesMessage reports whether msg matches the error message sought after by
    67  // the stubmethods fix.
    68  func MatchesMessage(msg string) bool {
    69  	return strings.Contains(msg, "missing method") || strings.HasPrefix(msg, "cannot convert") || strings.Contains(msg, "not implement")
    70  }
    71  
    72  // DiagnosticForError computes a diagnostic suggesting to implement an
    73  // interface to fix the type checking error defined by (start, end, msg).
    74  //
    75  // If no such fix is possible, the second result is false.
    76  func DiagnosticForError(fset *token.FileSet, file *ast.File, start, end token.Pos, msg string, info *types.Info) (analysis.Diagnostic, bool) {
    77  	if !MatchesMessage(msg) {
    78  		return analysis.Diagnostic{}, false
    79  	}
    80  
    81  	path, _ := astutil.PathEnclosingInterval(file, start, end)
    82  	si := GetStubInfo(fset, info, path, start)
    83  	if si == nil {
    84  		return analysis.Diagnostic{}, false
    85  	}
    86  	qf := typesutil.FileQualifier(file, si.Concrete.Obj().Pkg(), info)
    87  	iface := types.TypeString(si.Interface.Type(), qf)
    88  	return analysis.Diagnostic{
    89  		Pos:      start,
    90  		End:      end,
    91  		Message:  msg,
    92  		Category: FixCategory,
    93  		SuggestedFixes: []analysis.SuggestedFix{{
    94  			Message: fmt.Sprintf("Declare missing methods of %s", iface),
    95  			// No TextEdits => computed later by gopls.
    96  		}},
    97  	}, true
    98  }
    99  
   100  const FixCategory = "stubmethods" // recognized by gopls ApplyFix
   101  
   102  // StubInfo represents a concrete type
   103  // that wants to stub out an interface type
   104  type StubInfo struct {
   105  	// Interface is the interface that the client wants to implement.
   106  	// When the interface is defined, the underlying object will be a TypeName.
   107  	// Note that we keep track of types.Object instead of types.Type in order
   108  	// to keep a reference to the declaring object's package and the ast file
   109  	// in the case where the concrete type file requires a new import that happens to be renamed
   110  	// in the interface file.
   111  	// TODO(marwan-at-work): implement interface literals.
   112  	Fset      *token.FileSet // the FileSet used to type-check the types below
   113  	Interface *types.TypeName
   114  	Concrete  *types.Named
   115  	Pointer   bool
   116  }
   117  
   118  // GetStubInfo determines whether the "missing method error"
   119  // can be used to deduced what the concrete and interface types are.
   120  //
   121  // TODO(adonovan): this function (and its following 5 helpers) tries
   122  // to deduce a pair of (concrete, interface) types that are related by
   123  // an assignment, either explicitly or through a return statement or
   124  // function call. This is essentially what the refactor/satisfy does,
   125  // more generally. Refactor to share logic, after auditing 'satisfy'
   126  // for safety on ill-typed code.
   127  func GetStubInfo(fset *token.FileSet, info *types.Info, path []ast.Node, pos token.Pos) *StubInfo {
   128  	for _, n := range path {
   129  		switch n := n.(type) {
   130  		case *ast.ValueSpec:
   131  			return fromValueSpec(fset, info, n, pos)
   132  		case *ast.ReturnStmt:
   133  			// An error here may not indicate a real error the user should know about, but it may.
   134  			// Therefore, it would be best to log it out for debugging/reporting purposes instead of ignoring
   135  			// it. However, event.Log takes a context which is not passed via the analysis package.
   136  			// TODO(marwan-at-work): properly log this error.
   137  			si, _ := fromReturnStmt(fset, info, pos, path, n)
   138  			return si
   139  		case *ast.AssignStmt:
   140  			return fromAssignStmt(fset, info, n, pos)
   141  		case *ast.CallExpr:
   142  			// Note that some call expressions don't carry the interface type
   143  			// because they don't point to a function or method declaration elsewhere.
   144  			// For eaxmple, "var Interface = (*Concrete)(nil)". In that case, continue
   145  			// this loop to encounter other possibilities such as *ast.ValueSpec or others.
   146  			si := fromCallExpr(fset, info, pos, n)
   147  			if si != nil {
   148  				return si
   149  			}
   150  		}
   151  	}
   152  	return nil
   153  }
   154  
   155  // fromCallExpr tries to find an *ast.CallExpr's function declaration and
   156  // analyzes a function call's signature against the passed in parameter to deduce
   157  // the concrete and interface types.
   158  func fromCallExpr(fset *token.FileSet, info *types.Info, pos token.Pos, call *ast.CallExpr) *StubInfo {
   159  	// Find argument containing pos.
   160  	argIdx := -1
   161  	var arg ast.Expr
   162  	for i, callArg := range call.Args {
   163  		if callArg.Pos() <= pos && pos <= callArg.End() {
   164  			argIdx = i
   165  			arg = callArg
   166  			break
   167  		}
   168  	}
   169  	if arg == nil {
   170  		return nil
   171  	}
   172  
   173  	concType, pointer := concreteType(arg, info)
   174  	if concType == nil || concType.Obj().Pkg() == nil {
   175  		return nil
   176  	}
   177  	tv, ok := info.Types[call.Fun]
   178  	if !ok {
   179  		return nil
   180  	}
   181  	sig, ok := tv.Type.(*types.Signature)
   182  	if !ok {
   183  		return nil
   184  	}
   185  	var paramType types.Type
   186  	if sig.Variadic() && argIdx >= sig.Params().Len()-1 {
   187  		v := sig.Params().At(sig.Params().Len() - 1)
   188  		if s, _ := v.Type().(*types.Slice); s != nil {
   189  			paramType = s.Elem()
   190  		}
   191  	} else if argIdx < sig.Params().Len() {
   192  		paramType = sig.Params().At(argIdx).Type()
   193  	}
   194  	if paramType == nil {
   195  		return nil // A type error prevents us from determining the param type.
   196  	}
   197  	iface := ifaceObjFromType(paramType)
   198  	if iface == nil {
   199  		return nil
   200  	}
   201  	return &StubInfo{
   202  		Fset:      fset,
   203  		Concrete:  concType,
   204  		Pointer:   pointer,
   205  		Interface: iface,
   206  	}
   207  }
   208  
   209  // fromReturnStmt analyzes a "return" statement to extract
   210  // a concrete type that is trying to be returned as an interface type.
   211  //
   212  // For example, func() io.Writer { return myType{} }
   213  // would return StubInfo with the interface being io.Writer and the concrete type being myType{}.
   214  func fromReturnStmt(fset *token.FileSet, info *types.Info, pos token.Pos, path []ast.Node, ret *ast.ReturnStmt) (*StubInfo, error) {
   215  	// Find return operand containing pos.
   216  	returnIdx := -1
   217  	for i, r := range ret.Results {
   218  		if r.Pos() <= pos && pos <= r.End() {
   219  			returnIdx = i
   220  			break
   221  		}
   222  	}
   223  	if returnIdx == -1 {
   224  		return nil, fmt.Errorf("pos %d not within return statement bounds: [%d-%d]", pos, ret.Pos(), ret.End())
   225  	}
   226  
   227  	concType, pointer := concreteType(ret.Results[returnIdx], info)
   228  	if concType == nil || concType.Obj().Pkg() == nil {
   229  		return nil, nil
   230  	}
   231  	funcType := enclosingFunction(path, info)
   232  	if funcType == nil {
   233  		return nil, fmt.Errorf("could not find the enclosing function of the return statement")
   234  	}
   235  	if len(funcType.Results.List) != len(ret.Results) {
   236  		return nil, fmt.Errorf("%d-operand return statement in %d-result function",
   237  			len(ret.Results),
   238  			len(funcType.Results.List))
   239  	}
   240  	iface := ifaceType(funcType.Results.List[returnIdx].Type, info)
   241  	if iface == nil {
   242  		return nil, nil
   243  	}
   244  	return &StubInfo{
   245  		Fset:      fset,
   246  		Concrete:  concType,
   247  		Pointer:   pointer,
   248  		Interface: iface,
   249  	}, nil
   250  }
   251  
   252  // fromValueSpec returns *StubInfo from a variable declaration such as
   253  // var x io.Writer = &T{}
   254  func fromValueSpec(fset *token.FileSet, info *types.Info, spec *ast.ValueSpec, pos token.Pos) *StubInfo {
   255  	// Find RHS element containing pos.
   256  	var rhs ast.Expr
   257  	for _, r := range spec.Values {
   258  		if r.Pos() <= pos && pos <= r.End() {
   259  			rhs = r
   260  			break
   261  		}
   262  	}
   263  	if rhs == nil {
   264  		return nil // e.g. pos was on the LHS (#64545)
   265  	}
   266  
   267  	// Possible implicit/explicit conversion to interface type?
   268  	ifaceNode := spec.Type // var _ myInterface = ...
   269  	if call, ok := rhs.(*ast.CallExpr); ok && ifaceNode == nil && len(call.Args) == 1 {
   270  		// var _ = myInterface(v)
   271  		ifaceNode = call.Fun
   272  		rhs = call.Args[0]
   273  	}
   274  	concType, pointer := concreteType(rhs, info)
   275  	if concType == nil || concType.Obj().Pkg() == nil {
   276  		return nil
   277  	}
   278  	ifaceObj := ifaceType(ifaceNode, info)
   279  	if ifaceObj == nil {
   280  		return nil
   281  	}
   282  	return &StubInfo{
   283  		Fset:      fset,
   284  		Concrete:  concType,
   285  		Interface: ifaceObj,
   286  		Pointer:   pointer,
   287  	}
   288  }
   289  
   290  // fromAssignStmt returns *StubInfo from a variable assignment such as
   291  // var x io.Writer
   292  // x = &T{}
   293  func fromAssignStmt(fset *token.FileSet, info *types.Info, assign *ast.AssignStmt, pos token.Pos) *StubInfo {
   294  	// The interface conversion error in an assignment is against the RHS:
   295  	//
   296  	//      var x io.Writer
   297  	//      x = &T{} // error: missing method
   298  	//          ^^^^
   299  	//
   300  	// Find RHS element containing pos.
   301  	var lhs, rhs ast.Expr
   302  	for i, r := range assign.Rhs {
   303  		if r.Pos() <= pos && pos <= r.End() {
   304  			if i >= len(assign.Lhs) {
   305  				// This should never happen as we would get a
   306  				// "cannot assign N values to M variables"
   307  				// before we get an interface conversion error.
   308  				// But be defensive.
   309  				return nil
   310  			}
   311  			lhs = assign.Lhs[i]
   312  			rhs = r
   313  			break
   314  		}
   315  	}
   316  	if lhs == nil || rhs == nil {
   317  		return nil
   318  	}
   319  
   320  	ifaceObj := ifaceType(lhs, info)
   321  	if ifaceObj == nil {
   322  		return nil
   323  	}
   324  	concType, pointer := concreteType(rhs, info)
   325  	if concType == nil || concType.Obj().Pkg() == nil {
   326  		return nil
   327  	}
   328  	return &StubInfo{
   329  		Fset:      fset,
   330  		Concrete:  concType,
   331  		Interface: ifaceObj,
   332  		Pointer:   pointer,
   333  	}
   334  }
   335  
   336  // ifaceType returns the named interface type to which e refers, if any.
   337  func ifaceType(e ast.Expr, info *types.Info) *types.TypeName {
   338  	tv, ok := info.Types[e]
   339  	if !ok {
   340  		return nil
   341  	}
   342  	return ifaceObjFromType(tv.Type)
   343  }
   344  
   345  func ifaceObjFromType(t types.Type) *types.TypeName {
   346  	named, ok := t.(*types.Named)
   347  	if !ok {
   348  		return nil
   349  	}
   350  	if !types.IsInterface(named) {
   351  		return nil
   352  	}
   353  	// Interfaces defined in the "builtin" package return nil a Pkg().
   354  	// But they are still real interfaces that we need to make a special case for.
   355  	// Therefore, protect gopls from panicking if a new interface type was added in the future.
   356  	if named.Obj().Pkg() == nil && named.Obj().Name() != "error" {
   357  		return nil
   358  	}
   359  	return named.Obj()
   360  }
   361  
   362  // concreteType tries to extract the *types.Named that defines
   363  // the concrete type given the ast.Expr where the "missing method"
   364  // or "conversion" errors happened. If the concrete type is something
   365  // that cannot have methods defined on it (such as basic types), this
   366  // method will return a nil *types.Named. The second return parameter
   367  // is a boolean that indicates whether the concreteType was defined as a
   368  // pointer or value.
   369  func concreteType(e ast.Expr, info *types.Info) (*types.Named, bool) {
   370  	tv, ok := info.Types[e]
   371  	if !ok {
   372  		return nil, false
   373  	}
   374  	typ := tv.Type
   375  	ptr, isPtr := typ.(*types.Pointer)
   376  	if isPtr {
   377  		typ = ptr.Elem()
   378  	}
   379  	named, ok := typ.(*types.Named)
   380  	if !ok {
   381  		return nil, false
   382  	}
   383  	return named, isPtr
   384  }
   385  
   386  // enclosingFunction returns the signature and type of the function
   387  // enclosing the given position.
   388  func enclosingFunction(path []ast.Node, info *types.Info) *ast.FuncType {
   389  	for _, node := range path {
   390  		switch t := node.(type) {
   391  		case *ast.FuncDecl:
   392  			if _, ok := info.Defs[t.Name]; ok {
   393  				return t.Type
   394  			}
   395  		case *ast.FuncLit:
   396  			if _, ok := info.Types[t]; ok {
   397  				return t.Type
   398  			}
   399  		}
   400  	}
   401  	return nil
   402  }