1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
// 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.
package main
import (
"go/ast"
"go/token"
"reflect"
)
type simplifier struct {
hasDotImport bool // package file contains: import . "some/import/path"
}
func (s *simplifier) Visit(node ast.Node) ast.Visitor {
switch n := node.(type) {
case *ast.CompositeLit:
// array, slice, and map composite literals may be simplified
outer := n
var eltType ast.Expr
switch typ := outer.Type.(type) {
case *ast.ArrayType:
eltType = typ.Elt
case *ast.MapType:
eltType = typ.Value
}
if eltType != nil {
typ := reflect.ValueOf(eltType)
for i, x := range outer.Elts {
px := &outer.Elts[i]
// look at value of indexed/named elements
if t, ok := x.(*ast.KeyValueExpr); ok {
x = t.Value
px = &t.Value
}
ast.Walk(s, x) // simplify x
// if the element is a composite literal and its literal type
// matches the outer literal's element type exactly, the inner
// literal type may be omitted
if inner, ok := x.(*ast.CompositeLit); ok {
if match(nil, typ, reflect.ValueOf(inner.Type)) {
inner.Type = nil
}
}
// if the outer literal's element type is a pointer type *T
// and the element is & of a composite literal of type T,
// the inner &T may be omitted.
if ptr, ok := eltType.(*ast.StarExpr); ok {
if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND {
if inner, ok := addr.X.(*ast.CompositeLit); ok {
if match(nil, reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) {
inner.Type = nil // drop T
*px = inner // drop &
}
}
}
}
}
// node was simplified - stop walk (there are no subnodes to simplify)
return nil
}
case *ast.SliceExpr:
// a slice expression of the form: s[a:len(s)]
// can be simplified to: s[a:]
// if s is "simple enough" (for now we only accept identifiers)
if n.Max != nil || s.hasDotImport {
// - 3-index slices always require the 2nd and 3rd index
// - if dot imports are present, we cannot be certain that an
// unresolved "len" identifier refers to the predefined len()
break
}
if s, _ := n.X.(*ast.Ident); s != nil && s.Obj != nil {
// the array/slice object is a single, resolved identifier
if call, _ := n.High.(*ast.CallExpr); call != nil && len(call.Args) == 1 && !call.Ellipsis.IsValid() {
// the high expression is a function call with a single argument
if fun, _ := call.Fun.(*ast.Ident); fun != nil && fun.Name == "len" && fun.Obj == nil {
// the function called is "len" and it is not locally defined; and
// because we don't have dot imports, it must be the predefined len()
if arg, _ := call.Args[0].(*ast.Ident); arg != nil && arg.Obj == s.Obj {
// the len argument is the array/slice object
n.High = nil
}
}
}
}
// Note: We could also simplify slice expressions of the form s[0:b] to s[:b]
// but we leave them as is since sometimes we want to be very explicit
// about the lower bound.
// An example where the 0 helps:
// x, y, z := b[0:2], b[2:4], b[4:6]
// An example where it does not:
// x, y := b[:n], b[n:]
case *ast.RangeStmt:
// - a range of the form: for x, _ = range v {...}
// can be simplified to: for x = range v {...}
// - a range of the form: for _ = range v {...}
// can be simplified to: for range v {...}
if isBlank(n.Value) {
n.Value = nil
}
if isBlank(n.Key) && n.Value == nil {
n.Key = nil
}
}
return s
}
func isBlank(x ast.Expr) bool {
ident, ok := x.(*ast.Ident)
return ok && ident.Name == "_"
}
func simplify(f *ast.File) {
var s simplifier
// determine if f contains dot imports
for _, imp := range f.Imports {
if imp.Name != nil && imp.Name.Name == "." {
s.hasDotImport = true
break
}
}
// remove empty declarations such as "const ()", etc
removeEmptyDeclGroups(f)
ast.Walk(&s, f)
}
func removeEmptyDeclGroups(f *ast.File) {
i := 0
for _, d := range f.Decls {
if g, ok := d.(*ast.GenDecl); !ok || !isEmpty(f, g) {
f.Decls[i] = d
i++
}
}
f.Decls = f.Decls[:i]
}
func isEmpty(f *ast.File, g *ast.GenDecl) bool {
if g.Doc != nil || g.Specs != nil {
return false
}
for _, c := range f.Comments {
// if there is a comment in the declaration, it is not considered empty
if g.Pos() <= c.Pos() && c.End() <= g.End() {
return false
}
}
return true
}
|