| // Copyright 2010 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. |
| |
| // This file contains the pieces of the tool that use typechecking from the go/types package. |
| |
| package main |
| |
| import ( |
| "go/ast" |
| "go/token" |
| |
| "golang.org/x/tools/go/types" |
| ) |
| |
| // imports is the canonical map of imported packages we need for typechecking. |
| // It is created during initialization. |
| var imports = make(map[string]*types.Package) |
| |
| var ( |
| errorType *types.Interface |
| stringerType *types.Interface // possibly nil |
| formatterType *types.Interface // possibly nil |
| ) |
| |
| func init() { |
| errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) |
| |
| if typ := importType("fmt", "Stringer"); typ != nil { |
| stringerType = typ.Underlying().(*types.Interface) |
| } |
| |
| if typ := importType("fmt", "Formatter"); typ != nil { |
| formatterType = typ.Underlying().(*types.Interface) |
| } |
| } |
| |
| // importType returns the type denoted by the qualified identifier |
| // path.name, and adds the respective package to the imports map |
| // as a side effect. In case of an error, importType returns nil. |
| func importType(path, name string) types.Type { |
| pkg, err := types.DefaultImport(imports, path) |
| if err != nil { |
| // This can happen if the package at path hasn't been compiled yet. |
| warnf("import failed: %v", err) |
| return nil |
| } |
| if obj, ok := pkg.Scope().Lookup(name).(*types.TypeName); ok { |
| return obj.Type() |
| } |
| warnf("invalid type name %q", name) |
| return nil |
| } |
| |
| func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error { |
| pkg.defs = make(map[*ast.Ident]types.Object) |
| pkg.uses = make(map[*ast.Ident]types.Object) |
| pkg.selectors = make(map[*ast.SelectorExpr]*types.Selection) |
| pkg.spans = make(map[types.Object]Span) |
| pkg.types = make(map[ast.Expr]types.TypeAndValue) |
| config := types.Config{ |
| // We provide the same packages map for all imports to ensure |
| // that everybody sees identical packages for the given paths. |
| Packages: imports, |
| // By providing a Config with our own error function, it will continue |
| // past the first error. There is no need for that function to do anything. |
| Error: func(error) {}, |
| } |
| info := &types.Info{ |
| Selections: pkg.selectors, |
| Types: pkg.types, |
| Defs: pkg.defs, |
| Uses: pkg.uses, |
| } |
| typesPkg, err := config.Check(pkg.path, fs, astFiles, info) |
| pkg.typesPkg = typesPkg |
| // update spans |
| for id, obj := range pkg.defs { |
| pkg.growSpan(id, obj) |
| } |
| for id, obj := range pkg.uses { |
| pkg.growSpan(id, obj) |
| } |
| return err |
| } |
| |
| // isStruct reports whether the composite literal c is a struct. |
| // If it is not (probably a struct), it returns a printable form of the type. |
| func (pkg *Package) isStruct(c *ast.CompositeLit) (bool, string) { |
| // Check that the CompositeLit's type is a slice or array (which needs no field keys), if possible. |
| typ := pkg.types[c].Type |
| // If it's a named type, pull out the underlying type. If it's not, the Underlying |
| // method returns the type itself. |
| actual := typ |
| if actual != nil { |
| actual = actual.Underlying() |
| } |
| if actual == nil { |
| // No type information available. Assume true, so we do the check. |
| return true, "" |
| } |
| switch actual.(type) { |
| case *types.Struct: |
| return true, typ.String() |
| default: |
| return false, "" |
| } |
| } |
| |
| // matchArgType reports an error if printf verb t is not appropriate |
| // for operand arg. |
| // |
| // typ is used only for recursive calls; external callers must supply nil. |
| // |
| // (Recursion arises from the compound types {map,chan,slice} which |
| // may be printed with %d etc. if that is appropriate for their element |
| // types.) |
| func (f *File) matchArgType(t printfArgType, typ types.Type, arg ast.Expr) bool { |
| return f.matchArgTypeInternal(t, typ, arg, make(map[types.Type]bool)) |
| } |
| |
| // matchArgTypeInternal is the internal version of matchArgType. It carries a map |
| // remembering what types are in progress so we don't recur when faced with recursive |
| // types or mutually recursive types. |
| func (f *File) matchArgTypeInternal(t printfArgType, typ types.Type, arg ast.Expr, inProgress map[types.Type]bool) bool { |
| // %v, %T accept any argument type. |
| if t == anyType { |
| return true |
| } |
| if typ == nil { |
| // external call |
| typ = f.pkg.types[arg].Type |
| if typ == nil { |
| return true // probably a type check problem |
| } |
| } |
| // If the type implements fmt.Formatter, we have nothing to check. |
| // formatterTyp may be nil - be conservative and check for Format method in that case. |
| if formatterType != nil && types.Implements(typ, formatterType) || f.hasMethod(typ, "Format") { |
| return true |
| } |
| // If we can use a string, might arg (dynamically) implement the Stringer or Error interface? |
| if t&argString != 0 { |
| if types.AssertableTo(errorType, typ) || stringerType != nil && types.AssertableTo(stringerType, typ) { |
| return true |
| } |
| } |
| |
| typ = typ.Underlying() |
| if inProgress[typ] { |
| // We're already looking at this type. The call that started it will take care of it. |
| return true |
| } |
| inProgress[typ] = true |
| |
| switch typ := typ.(type) { |
| case *types.Signature: |
| return t&argPointer != 0 |
| |
| case *types.Map: |
| // Recur: map[int]int matches %d. |
| return t&argPointer != 0 || |
| (f.matchArgTypeInternal(t, typ.Key(), arg, inProgress) && f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress)) |
| |
| case *types.Chan: |
| return t&argPointer != 0 |
| |
| case *types.Array: |
| // Same as slice. |
| if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { |
| return true // %s matches []byte |
| } |
| // Recur: []int matches %d. |
| return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem().Underlying(), arg, inProgress) |
| |
| case *types.Slice: |
| // Same as array. |
| if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { |
| return true // %s matches []byte |
| } |
| // Recur: []int matches %d. But watch out for |
| // type T []T |
| // If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below. |
| return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress) |
| |
| case *types.Pointer: |
| // Ugly, but dealing with an edge case: a known pointer to an invalid type, |
| // probably something from a failed import. |
| if typ.Elem().String() == "invalid type" { |
| if *verbose { |
| f.Warnf(arg.Pos(), "printf argument %v is pointer to invalid or unknown type", f.gofmt(arg)) |
| } |
| return true // special case |
| } |
| // If it's actually a pointer with %p, it prints as one. |
| if t == argPointer { |
| return true |
| } |
| // If it's pointer to struct, that's equivalent in our analysis to whether we can print the struct. |
| if str, ok := typ.Elem().Underlying().(*types.Struct); ok { |
| return f.matchStructArgType(t, str, arg, inProgress) |
| } |
| // The rest can print with %p as pointers, or as integers with %x etc. |
| return t&(argInt|argPointer) != 0 |
| |
| case *types.Struct: |
| return f.matchStructArgType(t, typ, arg, inProgress) |
| |
| case *types.Interface: |
| // If the static type of the argument is empty interface, there's little we can do. |
| // Example: |
| // func f(x interface{}) { fmt.Printf("%s", x) } |
| // Whether x is valid for %s depends on the type of the argument to f. One day |
| // we will be able to do better. For now, we assume that empty interface is OK |
| // but non-empty interfaces, with Stringer and Error handled above, are errors. |
| return typ.NumMethods() == 0 |
| |
| case *types.Basic: |
| switch typ.Kind() { |
| case types.UntypedBool, |
| types.Bool: |
| return t&argBool != 0 |
| |
| case types.UntypedInt, |
| types.Int, |
| types.Int8, |
| types.Int16, |
| types.Int32, |
| types.Int64, |
| types.Uint, |
| types.Uint8, |
| types.Uint16, |
| types.Uint32, |
| types.Uint64, |
| types.Uintptr: |
| return t&argInt != 0 |
| |
| case types.UntypedFloat, |
| types.Float32, |
| types.Float64: |
| return t&argFloat != 0 |
| |
| case types.UntypedComplex, |
| types.Complex64, |
| types.Complex128: |
| return t&argComplex != 0 |
| |
| case types.UntypedString, |
| types.String: |
| return t&argString != 0 |
| |
| case types.UnsafePointer: |
| return t&(argPointer|argInt) != 0 |
| |
| case types.UntypedRune: |
| return t&(argInt|argRune) != 0 |
| |
| case types.UntypedNil: |
| return t&argPointer != 0 // TODO? |
| |
| case types.Invalid: |
| if *verbose { |
| f.Warnf(arg.Pos(), "printf argument %v has invalid or unknown type", f.gofmt(arg)) |
| } |
| return true // Probably a type check problem. |
| } |
| panic("unreachable") |
| } |
| |
| return false |
| } |
| |
| // hasBasicType reports whether x's type is a types.Basic with the given kind. |
| func (f *File) hasBasicType(x ast.Expr, kind types.BasicKind) bool { |
| t := f.pkg.types[x].Type |
| if t != nil { |
| t = t.Underlying() |
| } |
| b, ok := t.(*types.Basic) |
| return ok && b.Kind() == kind |
| } |
| |
| // matchStructArgType reports whether all the elements of the struct match the expected |
| // type. For instance, with "%d" all the elements must be printable with the "%d" format. |
| func (f *File) matchStructArgType(t printfArgType, typ *types.Struct, arg ast.Expr, inProgress map[types.Type]bool) bool { |
| for i := 0; i < typ.NumFields(); i++ { |
| if !f.matchArgTypeInternal(t, typ.Field(i).Type(), arg, inProgress) { |
| return false |
| } |
| } |
| return true |
| } |
| |
| // numArgsInSignature tells how many formal arguments the function type |
| // being called has. |
| func (f *File) numArgsInSignature(call *ast.CallExpr) int { |
| // Check the type of the function or method declaration |
| typ := f.pkg.types[call.Fun].Type |
| if typ == nil { |
| return 0 |
| } |
| // The type must be a signature, but be sure for safety. |
| sig, ok := typ.(*types.Signature) |
| if !ok { |
| return 0 |
| } |
| return sig.Params().Len() |
| } |
| |
| // isErrorMethodCall reports whether the call is of a method with signature |
| // func Error() string |
| // where "string" is the universe's string type. We know the method is called "Error". |
| func (f *File) isErrorMethodCall(call *ast.CallExpr) bool { |
| typ := f.pkg.types[call].Type |
| if typ != nil { |
| // We know it's called "Error", so just check the function signature |
| // (stringerType has exactly one method, String). |
| if stringerType != nil && stringerType.NumMethods() == 1 { |
| return types.Identical(f.pkg.types[call.Fun].Type, stringerType.Method(0).Type()) |
| } |
| } |
| // Without types, we can still check by hand. |
| // Is it a selector expression? Otherwise it's a function call, not a method call. |
| sel, ok := call.Fun.(*ast.SelectorExpr) |
| if !ok { |
| return false |
| } |
| // The package is type-checked, so if there are no arguments, we're done. |
| if len(call.Args) > 0 { |
| return false |
| } |
| // Check the type of the method declaration |
| typ = f.pkg.types[sel].Type |
| if typ == nil { |
| return false |
| } |
| // The type must be a signature, but be sure for safety. |
| sig, ok := typ.(*types.Signature) |
| if !ok { |
| return false |
| } |
| // There must be a receiver for it to be a method call. Otherwise it is |
| // a function, not something that satisfies the error interface. |
| if sig.Recv() == nil { |
| return false |
| } |
| // There must be no arguments. Already verified by type checking, but be thorough. |
| if sig.Params().Len() > 0 { |
| return false |
| } |
| // Finally the real questions. |
| // There must be one result. |
| if sig.Results().Len() != 1 { |
| return false |
| } |
| // It must have return type "string" from the universe. |
| return sig.Results().At(0).Type() == types.Typ[types.String] |
| } |
| |
| // hasMethod reports whether the type contains a method with the given name. |
| // It is part of the workaround for Formatters and should be deleted when |
| // that workaround is no longer necessary. |
| // TODO: This could be better once issue 6259 is fixed. |
| func (f *File) hasMethod(typ types.Type, name string) bool { |
| // assume we have an addressable variable of type typ |
| obj, _, _ := types.LookupFieldOrMethod(typ, true, f.pkg.typesPkg, name) |
| _, ok := obj.(*types.Func) |
| return ok |
| } |