666 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			666 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2015 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.
 | 
						|
 | 
						|
// Unconvert removes redundant type conversions from Go packages.
 | 
						|
package unconvert
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"flag"
 | 
						|
	"fmt"
 | 
						|
	"go/ast"
 | 
						|
	"go/build"
 | 
						|
	"go/format"
 | 
						|
	"go/parser"
 | 
						|
	"go/token"
 | 
						|
	"go/types"
 | 
						|
	"io/ioutil"
 | 
						|
	"log"
 | 
						|
	"os"
 | 
						|
	"reflect"
 | 
						|
	"runtime/pprof"
 | 
						|
	"sort"
 | 
						|
	"sync"
 | 
						|
	"unicode"
 | 
						|
 | 
						|
	"github.com/kisielk/gotool"
 | 
						|
	"golang.org/x/text/width"
 | 
						|
	"golang.org/x/tools/go/loader"
 | 
						|
)
 | 
						|
 | 
						|
// Unnecessary conversions are identified by the position
 | 
						|
// of their left parenthesis within a source file.
 | 
						|
 | 
						|
type editSet map[token.Position]struct{}
 | 
						|
 | 
						|
type fileToEditSet map[string]editSet
 | 
						|
 | 
						|
func apply(file string, edits editSet) {
 | 
						|
	if len(edits) == 0 {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	fset := token.NewFileSet()
 | 
						|
	f, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
 | 
						|
	if err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Note: We modify edits during the walk.
 | 
						|
	v := editor{edits: edits, file: fset.File(f.Package)}
 | 
						|
	ast.Walk(&v, f)
 | 
						|
	if len(edits) != 0 {
 | 
						|
		log.Printf("%s: missing edits %s", file, edits)
 | 
						|
	}
 | 
						|
 | 
						|
	// TODO(mdempsky): Write to temporary file and rename.
 | 
						|
	var buf bytes.Buffer
 | 
						|
	err = format.Node(&buf, fset, f)
 | 
						|
	if err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = ioutil.WriteFile(file, buf.Bytes(), 0)
 | 
						|
	if err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type editor struct {
 | 
						|
	edits editSet
 | 
						|
	file  *token.File
 | 
						|
}
 | 
						|
 | 
						|
func (e *editor) Visit(n ast.Node) ast.Visitor {
 | 
						|
	if n == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	v := reflect.ValueOf(n).Elem()
 | 
						|
	for i, n := 0, v.NumField(); i < n; i++ {
 | 
						|
		switch f := v.Field(i).Addr().Interface().(type) {
 | 
						|
		case *ast.Expr:
 | 
						|
			e.rewrite(f)
 | 
						|
		case *[]ast.Expr:
 | 
						|
			for i := range *f {
 | 
						|
				e.rewrite(&(*f)[i])
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return e
 | 
						|
}
 | 
						|
 | 
						|
func (e *editor) rewrite(f *ast.Expr) {
 | 
						|
	call, ok := (*f).(*ast.CallExpr)
 | 
						|
	if !ok {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	pos := e.file.Position(call.Lparen)
 | 
						|
	if _, ok := e.edits[pos]; !ok {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	*f = call.Args[0]
 | 
						|
	delete(e.edits, pos)
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	cr = []byte{'\r'}
 | 
						|
	nl = []byte{'\n'}
 | 
						|
)
 | 
						|
 | 
						|
func print(conversions []token.Position) {
 | 
						|
	var file string
 | 
						|
	var lines [][]byte
 | 
						|
 | 
						|
	for _, pos := range conversions {
 | 
						|
		fmt.Printf("%s:%d:%d: unnecessary conversion\n", pos.Filename, pos.Line, pos.Column)
 | 
						|
		if *flagV {
 | 
						|
			if pos.Filename != file {
 | 
						|
				buf, err := ioutil.ReadFile(pos.Filename)
 | 
						|
				if err != nil {
 | 
						|
					log.Fatal(err)
 | 
						|
				}
 | 
						|
				file = pos.Filename
 | 
						|
				lines = bytes.Split(buf, nl)
 | 
						|
			}
 | 
						|
 | 
						|
			line := bytes.TrimSuffix(lines[pos.Line-1], cr)
 | 
						|
			fmt.Printf("%s\n", line)
 | 
						|
 | 
						|
			// For files processed by cgo, Column is the
 | 
						|
			// column location after cgo processing, which
 | 
						|
			// may be different than the source column
 | 
						|
			// that we want here. In lieu of a better
 | 
						|
			// heuristic for detecting this case, at least
 | 
						|
			// avoid panicking if column is out of bounds.
 | 
						|
			if pos.Column <= len(line) {
 | 
						|
				fmt.Printf("%s^\n", rub(line[:pos.Column-1]))
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Rub returns a copy of buf with all non-whitespace characters replaced
 | 
						|
// by spaces (like rubbing them out with white out).
 | 
						|
func rub(buf []byte) []byte {
 | 
						|
	// TODO(mdempsky): Handle combining characters?
 | 
						|
	var res bytes.Buffer
 | 
						|
	for _, r := range string(buf) {
 | 
						|
		if unicode.IsSpace(r) {
 | 
						|
			res.WriteRune(r)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		switch width.LookupRune(r).Kind() {
 | 
						|
		case width.EastAsianWide, width.EastAsianFullwidth:
 | 
						|
			res.WriteString("  ")
 | 
						|
		default:
 | 
						|
			res.WriteByte(' ')
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return res.Bytes()
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	flagAll        = flag.Bool("unconvert.all", false, "type check all GOOS and GOARCH combinations")
 | 
						|
	flagApply      = flag.Bool("unconvert.apply", false, "apply edits to source files")
 | 
						|
	flagCPUProfile = flag.String("unconvert.cpuprofile", "", "write CPU profile to file")
 | 
						|
	// TODO(mdempsky): Better description and maybe flag name.
 | 
						|
	flagSafe = flag.Bool("unconvert.safe", false, "be more conservative (experimental)")
 | 
						|
	flagV    = flag.Bool("unconvert.v", false, "verbose output")
 | 
						|
)
 | 
						|
 | 
						|
func usage() {
 | 
						|
	fmt.Fprintf(os.Stderr, "usage: unconvert [flags] [package ...]\n")
 | 
						|
	flag.PrintDefaults()
 | 
						|
}
 | 
						|
 | 
						|
func nomain() {
 | 
						|
	flag.Usage = usage
 | 
						|
	flag.Parse()
 | 
						|
 | 
						|
	if *flagCPUProfile != "" {
 | 
						|
		f, err := os.Create(*flagCPUProfile)
 | 
						|
		if err != nil {
 | 
						|
			log.Fatal(err)
 | 
						|
		}
 | 
						|
		pprof.StartCPUProfile(f)
 | 
						|
		defer pprof.StopCPUProfile()
 | 
						|
	}
 | 
						|
 | 
						|
	importPaths := gotool.ImportPaths(flag.Args())
 | 
						|
	if len(importPaths) == 0 {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var m fileToEditSet
 | 
						|
	if *flagAll {
 | 
						|
		m = mergeEdits(importPaths)
 | 
						|
	} else {
 | 
						|
		m = computeEdits(importPaths, build.Default.GOOS, build.Default.GOARCH, build.Default.CgoEnabled)
 | 
						|
	}
 | 
						|
 | 
						|
	if *flagApply {
 | 
						|
		var wg sync.WaitGroup
 | 
						|
		for f, e := range m {
 | 
						|
			wg.Add(1)
 | 
						|
			f, e := f, e
 | 
						|
			go func() {
 | 
						|
				defer wg.Done()
 | 
						|
				apply(f, e)
 | 
						|
			}()
 | 
						|
		}
 | 
						|
		wg.Wait()
 | 
						|
	} else {
 | 
						|
		var conversions []token.Position
 | 
						|
		for _, positions := range m {
 | 
						|
			for pos := range positions {
 | 
						|
				conversions = append(conversions, pos)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		sort.Sort(byPosition(conversions))
 | 
						|
		print(conversions)
 | 
						|
		if len(conversions) > 0 {
 | 
						|
			os.Exit(1)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func Run(prog *loader.Program) []token.Position {
 | 
						|
	m := computeEditsFromProg(prog)
 | 
						|
	var conversions []token.Position
 | 
						|
	for _, positions := range m {
 | 
						|
		for pos := range positions {
 | 
						|
			conversions = append(conversions, pos)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return conversions
 | 
						|
}
 | 
						|
 | 
						|
var plats = [...]struct {
 | 
						|
	goos, goarch string
 | 
						|
}{
 | 
						|
	// TODO(mdempsky): buildall.bash also builds linux-386-387 and linux-arm-arm5.
 | 
						|
	{"android", "386"},
 | 
						|
	{"android", "amd64"},
 | 
						|
	{"android", "arm"},
 | 
						|
	{"android", "arm64"},
 | 
						|
	{"darwin", "386"},
 | 
						|
	{"darwin", "amd64"},
 | 
						|
	{"darwin", "arm"},
 | 
						|
	{"darwin", "arm64"},
 | 
						|
	{"dragonfly", "amd64"},
 | 
						|
	{"freebsd", "386"},
 | 
						|
	{"freebsd", "amd64"},
 | 
						|
	{"freebsd", "arm"},
 | 
						|
	{"linux", "386"},
 | 
						|
	{"linux", "amd64"},
 | 
						|
	{"linux", "arm"},
 | 
						|
	{"linux", "arm64"},
 | 
						|
	{"linux", "mips64"},
 | 
						|
	{"linux", "mips64le"},
 | 
						|
	{"linux", "ppc64"},
 | 
						|
	{"linux", "ppc64le"},
 | 
						|
	{"linux", "s390x"},
 | 
						|
	{"nacl", "386"},
 | 
						|
	{"nacl", "amd64p32"},
 | 
						|
	{"nacl", "arm"},
 | 
						|
	{"netbsd", "386"},
 | 
						|
	{"netbsd", "amd64"},
 | 
						|
	{"netbsd", "arm"},
 | 
						|
	{"openbsd", "386"},
 | 
						|
	{"openbsd", "amd64"},
 | 
						|
	{"openbsd", "arm"},
 | 
						|
	{"plan9", "386"},
 | 
						|
	{"plan9", "amd64"},
 | 
						|
	{"plan9", "arm"},
 | 
						|
	{"solaris", "amd64"},
 | 
						|
	{"windows", "386"},
 | 
						|
	{"windows", "amd64"},
 | 
						|
}
 | 
						|
 | 
						|
func mergeEdits(importPaths []string) fileToEditSet {
 | 
						|
	m := make(fileToEditSet)
 | 
						|
	for _, plat := range plats {
 | 
						|
		for f, e := range computeEdits(importPaths, plat.goos, plat.goarch, false) {
 | 
						|
			if e0, ok := m[f]; ok {
 | 
						|
				for k := range e0 {
 | 
						|
					if _, ok := e[k]; !ok {
 | 
						|
						delete(e0, k)
 | 
						|
					}
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				m[f] = e
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return m
 | 
						|
}
 | 
						|
 | 
						|
type noImporter struct{}
 | 
						|
 | 
						|
func (noImporter) Import(path string) (*types.Package, error) {
 | 
						|
	panic("golang.org/x/tools/go/loader said this wouldn't be called")
 | 
						|
}
 | 
						|
 | 
						|
func computeEdits(importPaths []string, os, arch string, cgoEnabled bool) fileToEditSet {
 | 
						|
	ctxt := build.Default
 | 
						|
	ctxt.GOOS = os
 | 
						|
	ctxt.GOARCH = arch
 | 
						|
	ctxt.CgoEnabled = cgoEnabled
 | 
						|
 | 
						|
	var conf loader.Config
 | 
						|
	conf.Build = &ctxt
 | 
						|
	conf.TypeChecker.Importer = noImporter{}
 | 
						|
	for _, importPath := range importPaths {
 | 
						|
		conf.Import(importPath)
 | 
						|
	}
 | 
						|
	prog, err := conf.Load()
 | 
						|
	if err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return computeEditsFromProg(prog)
 | 
						|
}
 | 
						|
 | 
						|
func computeEditsFromProg(prog *loader.Program) fileToEditSet {
 | 
						|
	type res struct {
 | 
						|
		file  string
 | 
						|
		edits editSet
 | 
						|
	}
 | 
						|
	ch := make(chan res)
 | 
						|
	var wg sync.WaitGroup
 | 
						|
	for _, pkg := range prog.InitialPackages() {
 | 
						|
		for _, file := range pkg.Files {
 | 
						|
			pkg, file := pkg, file
 | 
						|
			wg.Add(1)
 | 
						|
			go func() {
 | 
						|
				defer wg.Done()
 | 
						|
				v := visitor{pkg: pkg, file: prog.Fset.File(file.Package), edits: make(editSet)}
 | 
						|
				ast.Walk(&v, file)
 | 
						|
				ch <- res{v.file.Name(), v.edits}
 | 
						|
			}()
 | 
						|
		}
 | 
						|
	}
 | 
						|
	go func() {
 | 
						|
		wg.Wait()
 | 
						|
		close(ch)
 | 
						|
	}()
 | 
						|
 | 
						|
	m := make(fileToEditSet)
 | 
						|
	for r := range ch {
 | 
						|
		m[r.file] = r.edits
 | 
						|
	}
 | 
						|
	return m
 | 
						|
}
 | 
						|
 | 
						|
type step struct {
 | 
						|
	n ast.Node
 | 
						|
	i int
 | 
						|
}
 | 
						|
 | 
						|
type visitor struct {
 | 
						|
	pkg   *loader.PackageInfo
 | 
						|
	file  *token.File
 | 
						|
	edits editSet
 | 
						|
	path  []step
 | 
						|
}
 | 
						|
 | 
						|
func (v *visitor) Visit(node ast.Node) ast.Visitor {
 | 
						|
	if node != nil {
 | 
						|
		v.path = append(v.path, step{n: node})
 | 
						|
	} else {
 | 
						|
		n := len(v.path)
 | 
						|
		v.path = v.path[:n-1]
 | 
						|
		if n >= 2 {
 | 
						|
			v.path[n-2].i++
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if call, ok := node.(*ast.CallExpr); ok {
 | 
						|
		v.unconvert(call)
 | 
						|
	}
 | 
						|
	return v
 | 
						|
}
 | 
						|
 | 
						|
func (v *visitor) unconvert(call *ast.CallExpr) {
 | 
						|
	// TODO(mdempsky): Handle useless multi-conversions.
 | 
						|
 | 
						|
	// Conversions have exactly one argument.
 | 
						|
	if len(call.Args) != 1 || call.Ellipsis != token.NoPos {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	ft, ok := v.pkg.Types[call.Fun]
 | 
						|
	if !ok {
 | 
						|
		fmt.Println("Missing type for function")
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if !ft.IsType() {
 | 
						|
		// Function call; not a conversion.
 | 
						|
		return
 | 
						|
	}
 | 
						|
	at, ok := v.pkg.Types[call.Args[0]]
 | 
						|
	if !ok {
 | 
						|
		fmt.Println("Missing type for argument")
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if !types.Identical(ft.Type, at.Type) {
 | 
						|
		// A real conversion.
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if isUntypedValue(call.Args[0], &v.pkg.Info) {
 | 
						|
		// Workaround golang.org/issue/13061.
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if *flagSafe && !v.isSafeContext(at.Type) {
 | 
						|
		// TODO(mdempsky): Remove this message.
 | 
						|
		fmt.Println("Skipped a possible type conversion because of -safe at", v.file.Position(call.Pos()))
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if v.isCgoCheckPointerContext() {
 | 
						|
		// cmd/cgo generates explicit type conversions that
 | 
						|
		// are often redundant when introducing
 | 
						|
		// _cgoCheckPointer calls (issue #16).  Users can't do
 | 
						|
		// anything about these, so skip over them.
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	v.edits[v.file.Position(call.Lparen)] = struct{}{}
 | 
						|
}
 | 
						|
 | 
						|
func (v *visitor) isCgoCheckPointerContext() bool {
 | 
						|
	ctxt := &v.path[len(v.path)-2]
 | 
						|
	if ctxt.i != 1 {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	call, ok := ctxt.n.(*ast.CallExpr)
 | 
						|
	if !ok {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	ident, ok := call.Fun.(*ast.Ident)
 | 
						|
	if !ok {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return ident.Name == "_cgoCheckPointer"
 | 
						|
}
 | 
						|
 | 
						|
// isSafeContext reports whether the current context requires
 | 
						|
// an expression of type t.
 | 
						|
//
 | 
						|
// TODO(mdempsky): That's a bad explanation.
 | 
						|
func (v *visitor) isSafeContext(t types.Type) bool {
 | 
						|
	ctxt := &v.path[len(v.path)-2]
 | 
						|
	switch n := ctxt.n.(type) {
 | 
						|
	case *ast.AssignStmt:
 | 
						|
		pos := ctxt.i - len(n.Lhs)
 | 
						|
		if pos < 0 {
 | 
						|
			fmt.Println("Type conversion on LHS of assignment?")
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		if n.Tok == token.DEFINE {
 | 
						|
			// Skip := assignments.
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		// We're a conversion in the pos'th element of n.Rhs.
 | 
						|
		// Check that the corresponding element of n.Lhs is of type t.
 | 
						|
		lt, ok := v.pkg.Types[n.Lhs[pos]]
 | 
						|
		if !ok {
 | 
						|
			fmt.Println("Missing type for LHS expression")
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		return types.Identical(t, lt.Type)
 | 
						|
	case *ast.BinaryExpr:
 | 
						|
		if n.Op == token.SHL || n.Op == token.SHR {
 | 
						|
			if ctxt.i == 1 {
 | 
						|
				// RHS of a shift is always safe.
 | 
						|
				return true
 | 
						|
			}
 | 
						|
			// For the LHS, we should inspect up another level.
 | 
						|
			fmt.Println("TODO(mdempsky): Handle LHS of shift expressions")
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		var other ast.Expr
 | 
						|
		if ctxt.i == 0 {
 | 
						|
			other = n.Y
 | 
						|
		} else {
 | 
						|
			other = n.X
 | 
						|
		}
 | 
						|
		ot, ok := v.pkg.Types[other]
 | 
						|
		if !ok {
 | 
						|
			fmt.Println("Missing type for other binop subexpr")
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		return types.Identical(t, ot.Type)
 | 
						|
	case *ast.CallExpr:
 | 
						|
		pos := ctxt.i - 1
 | 
						|
		if pos < 0 {
 | 
						|
			// Type conversion in the function subexpr is okay.
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		ft, ok := v.pkg.Types[n.Fun]
 | 
						|
		if !ok {
 | 
						|
			fmt.Println("Missing type for function expression")
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		sig, ok := ft.Type.(*types.Signature)
 | 
						|
		if !ok {
 | 
						|
			// "Function" is either a type conversion (ok) or a builtin (ok?).
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		params := sig.Params()
 | 
						|
		var pt types.Type
 | 
						|
		if sig.Variadic() && n.Ellipsis == token.NoPos && pos >= params.Len()-1 {
 | 
						|
			pt = params.At(params.Len() - 1).Type().(*types.Slice).Elem()
 | 
						|
		} else {
 | 
						|
			pt = params.At(pos).Type()
 | 
						|
		}
 | 
						|
		return types.Identical(t, pt)
 | 
						|
	case *ast.CompositeLit, *ast.KeyValueExpr:
 | 
						|
		fmt.Println("TODO(mdempsky): Compare against value type of composite literal type at", v.file.Position(n.Pos()))
 | 
						|
		return true
 | 
						|
	case *ast.ReturnStmt:
 | 
						|
		// TODO(mdempsky): Is there a better way to get the corresponding
 | 
						|
		// return parameter type?
 | 
						|
		var funcType *ast.FuncType
 | 
						|
		for i := len(v.path) - 1; funcType == nil && i >= 0; i-- {
 | 
						|
			switch f := v.path[i].n.(type) {
 | 
						|
			case *ast.FuncDecl:
 | 
						|
				funcType = f.Type
 | 
						|
			case *ast.FuncLit:
 | 
						|
				funcType = f.Type
 | 
						|
			}
 | 
						|
		}
 | 
						|
		var typeExpr ast.Expr
 | 
						|
		for i, j := ctxt.i, 0; j < len(funcType.Results.List); j++ {
 | 
						|
			f := funcType.Results.List[j]
 | 
						|
			if len(f.Names) == 0 {
 | 
						|
				if i >= 1 {
 | 
						|
					i--
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				if i >= len(f.Names) {
 | 
						|
					i -= len(f.Names)
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			}
 | 
						|
			typeExpr = f.Type
 | 
						|
			break
 | 
						|
		}
 | 
						|
		if typeExpr == nil {
 | 
						|
			fmt.Println(ctxt)
 | 
						|
		}
 | 
						|
		pt, ok := v.pkg.Types[typeExpr]
 | 
						|
		if !ok {
 | 
						|
			fmt.Println("Missing type for return parameter at", v.file.Position(n.Pos()))
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		return types.Identical(t, pt.Type)
 | 
						|
	case *ast.StarExpr, *ast.UnaryExpr:
 | 
						|
		// TODO(mdempsky): I think these are always safe.
 | 
						|
		return true
 | 
						|
	case *ast.SwitchStmt:
 | 
						|
		// TODO(mdempsky): I think this is always safe?
 | 
						|
		return true
 | 
						|
	default:
 | 
						|
		// TODO(mdempsky): When can this happen?
 | 
						|
		fmt.Printf("... huh, %T at %v\n", n, v.file.Position(n.Pos()))
 | 
						|
		return true
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func isUntypedValue(n ast.Expr, info *types.Info) (res bool) {
 | 
						|
	switch n := n.(type) {
 | 
						|
	case *ast.BinaryExpr:
 | 
						|
		switch n.Op {
 | 
						|
		case token.SHL, token.SHR:
 | 
						|
			// Shifts yield an untyped value if their LHS is untyped.
 | 
						|
			return isUntypedValue(n.X, info)
 | 
						|
		case token.EQL, token.NEQ, token.LSS, token.GTR, token.LEQ, token.GEQ:
 | 
						|
			// Comparisons yield an untyped boolean value.
 | 
						|
			return true
 | 
						|
		case token.ADD, token.SUB, token.MUL, token.QUO, token.REM,
 | 
						|
			token.AND, token.OR, token.XOR, token.AND_NOT,
 | 
						|
			token.LAND, token.LOR:
 | 
						|
			return isUntypedValue(n.X, info) && isUntypedValue(n.Y, info)
 | 
						|
		}
 | 
						|
	case *ast.UnaryExpr:
 | 
						|
		switch n.Op {
 | 
						|
		case token.ADD, token.SUB, token.NOT, token.XOR:
 | 
						|
			return isUntypedValue(n.X, info)
 | 
						|
		}
 | 
						|
	case *ast.BasicLit:
 | 
						|
		// Basic literals are always untyped.
 | 
						|
		return true
 | 
						|
	case *ast.ParenExpr:
 | 
						|
		return isUntypedValue(n.X, info)
 | 
						|
	case *ast.SelectorExpr:
 | 
						|
		return isUntypedValue(n.Sel, info)
 | 
						|
	case *ast.Ident:
 | 
						|
		if obj, ok := info.Uses[n]; ok {
 | 
						|
			if obj.Pkg() == nil && obj.Name() == "nil" {
 | 
						|
				// The universal untyped zero value.
 | 
						|
				return true
 | 
						|
			}
 | 
						|
			if b, ok := obj.Type().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
 | 
						|
				// Reference to an untyped constant.
 | 
						|
				return true
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case *ast.CallExpr:
 | 
						|
		if b, ok := asBuiltin(n.Fun, info); ok {
 | 
						|
			switch b.Name() {
 | 
						|
			case "real", "imag":
 | 
						|
				return isUntypedValue(n.Args[0], info)
 | 
						|
			case "complex":
 | 
						|
				return isUntypedValue(n.Args[0], info) && isUntypedValue(n.Args[1], info)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func asBuiltin(n ast.Expr, info *types.Info) (*types.Builtin, bool) {
 | 
						|
	for {
 | 
						|
		paren, ok := n.(*ast.ParenExpr)
 | 
						|
		if !ok {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		n = paren.X
 | 
						|
	}
 | 
						|
 | 
						|
	ident, ok := n.(*ast.Ident)
 | 
						|
	if !ok {
 | 
						|
		return nil, false
 | 
						|
	}
 | 
						|
 | 
						|
	obj, ok := info.Uses[ident]
 | 
						|
	if !ok {
 | 
						|
		return nil, false
 | 
						|
	}
 | 
						|
 | 
						|
	b, ok := obj.(*types.Builtin)
 | 
						|
	return b, ok
 | 
						|
}
 | 
						|
 | 
						|
type byPosition []token.Position
 | 
						|
 | 
						|
func (p byPosition) Len() int {
 | 
						|
	return len(p)
 | 
						|
}
 | 
						|
 | 
						|
func (p byPosition) Less(i, j int) bool {
 | 
						|
	if p[i].Filename != p[j].Filename {
 | 
						|
		return p[i].Filename < p[j].Filename
 | 
						|
	}
 | 
						|
	if p[i].Line != p[j].Line {
 | 
						|
		return p[i].Line < p[j].Line
 | 
						|
	}
 | 
						|
	return p[i].Column < p[j].Column
 | 
						|
}
 | 
						|
 | 
						|
func (p byPosition) Swap(i, j int) {
 | 
						|
	p[i], p[j] = p[j], p[i]
 | 
						|
}
 |