Add self-contained gometalinter build tooling.
This commit is contained in:
27
tools/vendor/github.com/mdempsky/unconvert/LICENSE
generated
vendored
Normal file
27
tools/vendor/github.com/mdempsky/unconvert/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
36
tools/vendor/github.com/mdempsky/unconvert/README
generated
vendored
Normal file
36
tools/vendor/github.com/mdempsky/unconvert/README
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
About:
|
||||
|
||||
The unconvert program analyzes Go packages to identify unnecessary
|
||||
type conversions; i.e., expressions T(x) where x already has type T.
|
||||
|
||||
Install:
|
||||
|
||||
$ go get github.com/mdempsky/unconvert
|
||||
|
||||
Usage:
|
||||
|
||||
$ unconvert -v bytes fmt
|
||||
GOROOT/src/bytes/reader.go:117:14: unnecessary conversion
|
||||
abs = int64(r.i) + offset
|
||||
^
|
||||
GOROOT/src/fmt/print.go:411:21: unnecessary conversion
|
||||
p.fmt.integer(int64(v), 16, unsigned, udigits)
|
||||
^
|
||||
|
||||
Flags:
|
||||
|
||||
Using the -v flag, unconvert will also print the source line and a
|
||||
caret to indicate the unnecessary conversion's position therein.
|
||||
|
||||
Using the -apply flag, unconvert will rewrite the Go source files
|
||||
without the unnecessary type conversions.
|
||||
|
||||
Using the -all flag, unconvert will analyze the Go packages under all
|
||||
possible GOOS/GOARCH combinations, and only identify conversions that
|
||||
are unnecessary in all cases.
|
||||
|
||||
E.g., syscall.Timespec's Sec and Nsec fields are int64 under
|
||||
linux/amd64 but int32 under linux/386. An int64(ts.Sec) conversion
|
||||
that appears in a linux/amd64-only file will be identified as
|
||||
unnecessary, but it will be preserved if it occurs in a file that's
|
||||
compiled for both linux/amd64 and linux/386.
|
||||
650
tools/vendor/github.com/mdempsky/unconvert/unconvert.go
generated
vendored
Normal file
650
tools/vendor/github.com/mdempsky/unconvert/unconvert.go
generated
vendored
Normal file
@@ -0,0 +1,650 @@
|
||||
// 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 main
|
||||
|
||||
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("all", false, "type check all GOOS and GOARCH combinations")
|
||||
flagApply = flag.Bool("apply", false, "apply edits to source files")
|
||||
flagCPUProfile = flag.String("cpuprofile", "", "write CPU profile to file")
|
||||
// TODO(mdempsky): Better description and maybe flag name.
|
||||
flagSafe = flag.Bool("safe", false, "be more conservative (experimental)")
|
||||
flagV = flag.Bool("v", false, "verbose output")
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "usage: unconvert [flags] [package ...]\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func main() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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: conf.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]
|
||||
}
|
||||
Reference in New Issue
Block a user