mirror of
https://github.com/autc04/Retro68.git
synced 2024-09-08 20:55:01 +00:00
314 lines
9.2 KiB
Go
314 lines
9.2 KiB
Go
// 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/build"
|
|
"go/importer"
|
|
"go/token"
|
|
"go/types"
|
|
"runtime"
|
|
)
|
|
|
|
// stdImporter is the importer we use to import packages.
|
|
// It is shared so that all packages are imported by the same importer.
|
|
var stdImporter types.Importer
|
|
|
|
var (
|
|
errorType *types.Interface
|
|
stringerType *types.Interface // possibly nil
|
|
formatterType *types.Interface // possibly nil
|
|
)
|
|
|
|
func inittypes() {
|
|
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)
|
|
}
|
|
}
|
|
|
|
// isNamedType reports whether t is the named type path.name.
|
|
func isNamedType(t types.Type, path, name string) bool {
|
|
n, ok := t.(*types.Named)
|
|
if !ok {
|
|
return false
|
|
}
|
|
obj := n.Obj()
|
|
return obj.Name() == name && obj.Pkg() != nil && obj.Pkg().Path() == path
|
|
}
|
|
|
|
// 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 := stdImporter.Import(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 {
|
|
if stdImporter == nil {
|
|
if *source {
|
|
stdImporter = importer.For("source", nil)
|
|
} else {
|
|
stdImporter = importer.Default()
|
|
}
|
|
inittypes()
|
|
}
|
|
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)
|
|
|
|
var allErrors []error
|
|
config := types.Config{
|
|
// We use the same importer for all imports to ensure that
|
|
// everybody sees identical packages for the given paths.
|
|
Importer: stdImporter,
|
|
// By providing a Config with our own error function, it will continue
|
|
// past the first error. We collect them all for printing later.
|
|
Error: func(e error) {
|
|
allErrors = append(allErrors, e)
|
|
},
|
|
|
|
Sizes: archSizes,
|
|
}
|
|
info := &types.Info{
|
|
Selections: pkg.selectors,
|
|
Types: pkg.types,
|
|
Defs: pkg.defs,
|
|
Uses: pkg.uses,
|
|
}
|
|
typesPkg, err := config.Check(pkg.path, fs, astFiles, info)
|
|
if len(allErrors) == 0 && err != nil {
|
|
allErrors = append(allErrors, err)
|
|
}
|
|
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 allErrors
|
|
}
|
|
|
|
// 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.
|
|
if f.isFormatter(typ) {
|
|
return true
|
|
}
|
|
// If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
|
|
if t&argString != 0 && isConvertibleToString(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:
|
|
// There's little we can do.
|
|
// Whether any particular verb is valid depends on the argument.
|
|
// The user may have reasonable prior knowledge of the contents of the interface.
|
|
return true
|
|
|
|
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
|
|
}
|
|
|
|
func isConvertibleToString(typ types.Type) bool {
|
|
return types.AssertableTo(errorType, typ) || stringerType != nil && types.AssertableTo(stringerType, typ)
|
|
}
|
|
|
|
// 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++ {
|
|
typf := typ.Field(i)
|
|
if !f.matchArgTypeInternal(t, typf.Type(), arg, inProgress) {
|
|
return false
|
|
}
|
|
if t&argString != 0 && !typf.Exported() && isConvertibleToString(typf.Type()) {
|
|
// Issue #17798: unexported Stringer or error cannot be properly fomatted.
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
var archSizes = types.SizesFor(runtime.Compiler, build.Default.GOARCH)
|