| // Copyright 2022 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package stubmethods provides the analysis logic for the quick fix |
| // to "Declare missing methods of TYPE" errors. (The fix logic lives |
| // in golang.stubMethodsFixer.) |
| package stubmethods |
| |
| import ( |
| "bytes" |
| "fmt" |
| "go/ast" |
| "go/token" |
| "go/types" |
| "strings" |
| |
| "golang.org/x/tools/go/ast/edge" |
| "golang.org/x/tools/go/ast/inspector" |
| "golang.org/x/tools/internal/typesinternal" |
| |
| "golang.org/x/tools/gopls/internal/cache/parsego" |
| "golang.org/x/tools/gopls/internal/util/bug" |
| "golang.org/x/tools/gopls/internal/util/typesutil" |
| ) |
| |
| // TODO(adonovan): eliminate the confusing Fset parameter; only the |
| // file name and byte offset of Concrete are needed. |
| |
| // IfaceStubInfo represents a concrete type |
| // that wants to stub out an interface type |
| type IfaceStubInfo struct { |
| // Interface is the interface that the client wants to implement. |
| // When the interface is defined, the underlying object will be a TypeName. |
| // Note that we keep track of types.Object instead of types.Type in order |
| // to keep a reference to the declaring object's package and the ast file |
| // in the case where the concrete type file requires a new import that happens to be renamed |
| // in the interface file. |
| // TODO(marwan-at-work): implement interface literals. |
| Fset *token.FileSet // the FileSet used to type-check the types below |
| Interface *types.TypeName |
| Concrete typesinternal.NamedOrAlias |
| pointer bool |
| } |
| |
| // GetIfaceStubInfo determines whether the "missing method error" |
| // can be used to deduced what the concrete and interface types are. |
| // |
| // TODO(adonovan): this function (and its following 5 helpers) tries |
| // to deduce a pair of (concrete, interface) types that are related by |
| // an assignment, either explicitly or through a return statement or |
| // function call. This is essentially what the refactor/satisfy does, |
| // more generally. Refactor to share logic, after auditing 'satisfy' |
| // for safety on ill-typed code. |
| func GetIfaceStubInfo(fset *token.FileSet, info *types.Info, pgf *parsego.File, pos, end token.Pos) *IfaceStubInfo { |
| cur, _ := pgf.Cursor.FindByPos(pos, end) |
| for cur := range cur.Enclosing() { |
| // TODO: do cur = unparenEnclosing(cur) first, once CL 701035 lands. |
| ek, _ := cur.ParentEdge() |
| switch ek { |
| case edge.ValueSpec_Values: |
| return fromValueSpec(fset, info, cur) |
| case edge.ReturnStmt_Results: |
| // An error here may not indicate a real error the user should know about, but it may. |
| // Therefore, it would be best to log it out for debugging/reporting purposes instead of ignoring |
| // it. However, event.Log takes a context which is not passed via the analysis package. |
| // TODO(marwan-at-work): properly log this error. |
| si, _ := fromReturnStmt(fset, info, cur) |
| return si |
| case edge.AssignStmt_Rhs: |
| return fromAssignStmt(fset, info, cur) |
| case edge.CallExpr_Args: |
| // 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(fset, info, cur) |
| if si != nil { |
| return si |
| } |
| } |
| } |
| return nil |
| } |
| |
| // Emit writes to out the missing methods of si.Concrete required for it to implement si.Interface |
| func (si *IfaceStubInfo) Emit(out *bytes.Buffer, qual types.Qualifier) error { |
| conc := si.Concrete.Obj() |
| // Record all direct methods of the current object |
| concreteFuncs := make(map[string]struct{}) |
| if named, ok := types.Unalias(si.Concrete).(*types.Named); ok { |
| for i := 0; i < named.NumMethods(); i++ { |
| concreteFuncs[named.Method(i).Name()] = struct{}{} |
| } |
| } |
| |
| // Find subset of interface methods that the concrete type lacks. |
| ifaceType := si.Interface.Type().Underlying().(*types.Interface) |
| |
| type missingFn struct { |
| fn *types.Func |
| needSubtle string |
| } |
| |
| var ( |
| missing []missingFn |
| concreteStruct, isStruct = typesinternal.Origin(si.Concrete).Underlying().(*types.Struct) |
| ) |
| |
| for i := 0; i < ifaceType.NumMethods(); i++ { |
| imethod := ifaceType.Method(i) |
| cmethod, index, _ := types.LookupFieldOrMethod(si.Concrete, si.pointer, imethod.Pkg(), imethod.Name()) |
| if cmethod == nil { |
| missing = append(missing, missingFn{fn: imethod}) |
| continue |
| } |
| |
| if _, ok := cmethod.(*types.Var); ok { |
| // len(LookupFieldOrMethod.index) = 1 => conflict, >1 => shadow. |
| return fmt.Errorf("adding method %s.%s would conflict with (or shadow) existing field", |
| conc.Name(), imethod.Name()) |
| } |
| |
| if _, exist := concreteFuncs[imethod.Name()]; exist { |
| if !types.Identical(cmethod.Type(), imethod.Type()) { |
| return fmt.Errorf("method %s.%s already exists but has the wrong type: got %s, want %s", |
| conc.Name(), imethod.Name(), cmethod.Type(), imethod.Type()) |
| } |
| continue |
| } |
| |
| mf := missingFn{fn: imethod} |
| if isStruct && len(index) > 0 { |
| field := concreteStruct.Field(index[0]) |
| |
| fn := field.Name() |
| if _, ok := field.Type().(*types.Pointer); ok { |
| fn = "*" + fn |
| } |
| |
| mf.needSubtle = fmt.Sprintf("// Subtle: this method shadows the method (%s).%s of %s.%s.\n", fn, imethod.Name(), si.Concrete.Obj().Name(), field.Name()) |
| } |
| |
| missing = append(missing, mf) |
| } |
| if len(missing) == 0 { |
| return fmt.Errorf("no missing methods found") |
| } |
| |
| // Format interface name (used only in a comment). |
| iface := si.Interface.Name() |
| if ipkg := si.Interface.Pkg(); ipkg != nil && ipkg != conc.Pkg() { |
| iface = ipkg.Name() + "." + iface |
| } |
| |
| // Pointer receiver? |
| var star string |
| if si.pointer { |
| star = "*" |
| } |
| |
| // If there are any that have named receiver, choose the first one. |
| // Otherwise, use lowercase for the first letter of the object. |
| rn := strings.ToLower(si.Concrete.Obj().Name()[0:1]) |
| if named, ok := types.Unalias(si.Concrete).(*types.Named); ok { |
| for i := 0; i < named.NumMethods(); i++ { |
| if recv := named.Method(i).Type().(*types.Signature).Recv(); recv.Name() != "" { |
| rn = recv.Name() |
| break |
| } |
| } |
| } |
| |
| // Check for receiver name conflicts |
| checkRecvName := func(tuple *types.Tuple) bool { |
| for i := 0; i < tuple.Len(); i++ { |
| if rn == tuple.At(i).Name() { |
| return true |
| } |
| } |
| return false |
| } |
| |
| for index := range missing { |
| mrn := rn + " " |
| sig := missing[index].fn.Signature() |
| if checkRecvName(sig.Params()) || checkRecvName(sig.Results()) { |
| mrn = "" |
| } |
| |
| fmt.Fprintf(out, `// %s implements [%s]. |
| %sfunc (%s%s%s%s) %s%s { |
| panic("unimplemented") |
| } |
| `, |
| missing[index].fn.Name(), |
| iface, |
| missing[index].needSubtle, |
| mrn, |
| star, |
| si.Concrete.Obj().Name(), |
| typesutil.FormatTypeParams(si.Concrete.TypeParams()), |
| missing[index].fn.Name(), |
| strings.TrimPrefix(types.TypeString(missing[index].fn.Type(), qual), "func")) |
| } |
| return nil |
| } |
| |
| // fromCallExpr tries to find an *ast.CallExpr's function declaration and |
| // analyzes a function call's signature against the passed in call argument to deduce |
| // the concrete and interface types. |
| func fromCallExpr(fset *token.FileSet, info *types.Info, curCallArg inspector.Cursor) *IfaceStubInfo { |
| |
| call := curCallArg.Parent().Node().(*ast.CallExpr) |
| arg := curCallArg.Node().(ast.Expr) |
| |
| concType, pointer := concreteType(arg, info) |
| if concType == nil || concType.Obj().Pkg() == nil { |
| return nil |
| } |
| tv, ok := info.Types[call.Fun] |
| if !ok { |
| return nil |
| } |
| sig, ok := types.Unalias(tv.Type).(*types.Signature) |
| if !ok { |
| return nil |
| } |
| |
| _, argIdx := curCallArg.ParentEdge() |
| var paramType types.Type |
| if sig.Variadic() && argIdx >= sig.Params().Len()-1 { |
| v := sig.Params().At(sig.Params().Len() - 1) |
| if s, _ := v.Type().(*types.Slice); s != nil { |
| paramType = s.Elem() |
| } |
| } else if argIdx < sig.Params().Len() { |
| paramType = sig.Params().At(argIdx).Type() |
| } |
| if paramType == nil { |
| return nil // A type error prevents us from determining the param type. |
| } |
| iface := ifaceObjFromType(paramType) |
| if iface == nil { |
| return nil |
| } |
| return &IfaceStubInfo{ |
| Fset: fset, |
| Concrete: concType, |
| pointer: pointer, |
| Interface: iface, |
| } |
| } |
| |
| // fromReturnStmt analyzes a "return" statement to extract |
| // a concrete type that is trying to be returned as an interface type. |
| // |
| // For example, func() io.Writer { return myType{} } |
| // would return StubIfaceInfo with the interface being io.Writer and the concrete type being myType{}. |
| func fromReturnStmt(fset *token.FileSet, info *types.Info, curResult inspector.Cursor) (*IfaceStubInfo, error) { |
| concType, pointer := concreteType(curResult.Node().(ast.Expr), info) |
| if concType == nil || concType.Obj().Pkg() == nil { |
| return nil, nil // result is not a named or *named or alias thereof |
| } |
| // Inv: the return is not a spread return, |
| // such as "return f()" where f() has tuple type. |
| conc := concType.Obj() |
| if conc.Parent() != conc.Pkg().Scope() { |
| return nil, fmt.Errorf("local type %q cannot be stubbed", conc.Name()) |
| } |
| |
| sig := typesutil.EnclosingSignature(curResult, info) |
| if sig == nil { |
| // golang/go#70666: this bug may be reached in practice. |
| return nil, bug.Errorf("could not find the enclosing function of the return statement") |
| } |
| rets := sig.Results() |
| // The return operands and function results must match. |
| // (Spread returns were rejected earlier.) |
| ret := curResult.Parent().Node().(*ast.ReturnStmt) |
| if rets.Len() != len(ret.Results) { |
| return nil, fmt.Errorf("%d-operand return statement in %d-result function", |
| len(ret.Results), |
| rets.Len()) |
| } |
| _, resultIdx := curResult.ParentEdge() |
| iface := ifaceObjFromType(rets.At(resultIdx).Type()) |
| if iface == nil { |
| return nil, nil |
| } |
| return &IfaceStubInfo{ |
| Fset: fset, |
| Concrete: concType, |
| pointer: pointer, |
| Interface: iface, |
| }, nil |
| } |
| |
| // fromValueSpec returns *StubIfaceInfo from a variable declaration such as |
| // var x io.Writer = &T{} |
| func fromValueSpec(fset *token.FileSet, info *types.Info, curValue inspector.Cursor) *IfaceStubInfo { |
| |
| rhs := curValue.Node().(ast.Expr) |
| spec := curValue.Parent().Node().(*ast.ValueSpec) |
| |
| // Possible implicit/explicit conversion to interface type? |
| ifaceNode := spec.Type // var _ myInterface = ... |
| if call, ok := rhs.(*ast.CallExpr); ok && ifaceNode == nil && len(call.Args) == 1 { |
| // var _ = myInterface(v) |
| ifaceNode = call.Fun |
| rhs = call.Args[0] |
| } |
| concType, pointer := concreteType(rhs, info) |
| if concType == nil || concType.Obj().Pkg() == nil { |
| return nil |
| } |
| conc := concType.Obj() |
| if conc.Parent() != conc.Pkg().Scope() { |
| return nil |
| } |
| |
| ifaceObj := ifaceType(ifaceNode, info) |
| if ifaceObj == nil { |
| return nil |
| } |
| return &IfaceStubInfo{ |
| Fset: fset, |
| Concrete: concType, |
| Interface: ifaceObj, |
| pointer: pointer, |
| } |
| } |
| |
| // fromAssignStmt returns *StubIfaceInfo from a variable assignment such as |
| // var x io.Writer |
| // x = &T{} |
| func fromAssignStmt(fset *token.FileSet, info *types.Info, curRhs inspector.Cursor) *IfaceStubInfo { |
| // The interface conversion error in an assignment is against the RHS: |
| // |
| // var x io.Writer |
| // x = &T{} // error: missing method |
| // ^^^^ |
| |
| assign := curRhs.Parent().Node().(*ast.AssignStmt) |
| _, idx := curRhs.ParentEdge() |
| lhs, rhs := assign.Lhs[idx], curRhs.Node().(ast.Expr) |
| |
| ifaceObj := ifaceType(lhs, info) |
| if ifaceObj == nil { |
| return nil |
| } |
| concType, pointer := concreteType(rhs, info) |
| if concType == nil || concType.Obj().Pkg() == nil { |
| return nil |
| } |
| conc := concType.Obj() |
| if conc.Parent() != conc.Pkg().Scope() { |
| return nil |
| } |
| return &IfaceStubInfo{ |
| Fset: fset, |
| Concrete: concType, |
| Interface: ifaceObj, |
| pointer: pointer, |
| } |
| } |
| |
| // ifaceType returns the named interface type to which e refers, if any. |
| func ifaceType(e ast.Expr, info *types.Info) *types.TypeName { |
| tv, ok := info.Types[e] |
| if !ok { |
| return nil |
| } |
| return ifaceObjFromType(tv.Type) |
| } |
| |
| func ifaceObjFromType(t types.Type) *types.TypeName { |
| named, ok := types.Unalias(t).(*types.Named) |
| if !ok { |
| return nil |
| } |
| if !types.IsInterface(named) { |
| return nil |
| } |
| // Interfaces defined in the "builtin" package return nil a Pkg(). |
| // But they are still real interfaces that we need to make a special case for. |
| // Therefore, protect gopls from panicking if a new interface type was added in the future. |
| if named.Obj().Pkg() == nil && named.Obj().Name() != "error" { |
| return nil |
| } |
| return named.Obj() |
| } |
| |
| // concreteType tries to extract the *types.Named that defines |
| // the concrete type given the ast.Expr where the "missing method" |
| // or "conversion" errors happened. If the concrete type is something |
| // that cannot have methods defined on it (such as basic types), this |
| // method will return a nil *types.Named. The second return parameter |
| // is a boolean that indicates whether the concreteType was defined as a |
| // pointer or value. |
| func concreteType(e ast.Expr, info *types.Info) (*types.Named, bool) { |
| tv, ok := info.Types[e] |
| if !ok { |
| return nil, false |
| } |
| typ := tv.Type |
| ptr, isPtr := types.Unalias(typ).(*types.Pointer) |
| if isPtr { |
| typ = ptr.Elem() |
| } |
| named, ok := types.Unalias(typ).(*types.Named) |
| if !ok { |
| return nil, false |
| } |
| return named, isPtr |
| } |