golang.org/x/tools/gopls@v0.15.3/internal/analysis/simplifycompositelit/simplifycompositelit.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package simplifycompositelit defines an Analyzer that simplifies composite literals. 6 // https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go 7 // https://golang.org/cmd/gofmt/#hdr-The_simplify_command 8 package simplifycompositelit 9 10 import ( 11 "bytes" 12 _ "embed" 13 "fmt" 14 "go/ast" 15 "go/printer" 16 "go/token" 17 "reflect" 18 19 "golang.org/x/tools/go/analysis" 20 "golang.org/x/tools/go/analysis/passes/inspect" 21 "golang.org/x/tools/go/ast/inspector" 22 "golang.org/x/tools/internal/analysisinternal" 23 ) 24 25 //go:embed doc.go 26 var doc string 27 28 var Analyzer = &analysis.Analyzer{ 29 Name: "simplifycompositelit", 30 Doc: analysisinternal.MustExtractDoc(doc, "simplifycompositelit"), 31 Requires: []*analysis.Analyzer{inspect.Analyzer}, 32 Run: run, 33 URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifycompositelit", 34 } 35 36 func run(pass *analysis.Pass) (interface{}, error) { 37 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 38 nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)} 39 inspect.Preorder(nodeFilter, func(n ast.Node) { 40 expr := n.(*ast.CompositeLit) 41 42 outer := expr 43 var keyType, eltType ast.Expr 44 switch typ := outer.Type.(type) { 45 case *ast.ArrayType: 46 eltType = typ.Elt 47 case *ast.MapType: 48 keyType = typ.Key 49 eltType = typ.Value 50 } 51 52 if eltType == nil { 53 return 54 } 55 var ktyp reflect.Value 56 if keyType != nil { 57 ktyp = reflect.ValueOf(keyType) 58 } 59 typ := reflect.ValueOf(eltType) 60 for _, x := range outer.Elts { 61 // look at value of indexed/named elements 62 if t, ok := x.(*ast.KeyValueExpr); ok { 63 if keyType != nil { 64 simplifyLiteral(pass, ktyp, keyType, t.Key) 65 } 66 x = t.Value 67 } 68 simplifyLiteral(pass, typ, eltType, x) 69 } 70 }) 71 return nil, nil 72 } 73 74 func simplifyLiteral(pass *analysis.Pass, typ reflect.Value, astType, x ast.Expr) { 75 // if the element is a composite literal and its literal type 76 // matches the outer literal's element type exactly, the inner 77 // literal type may be omitted 78 if inner, ok := x.(*ast.CompositeLit); ok && match(typ, reflect.ValueOf(inner.Type)) { 79 var b bytes.Buffer 80 printer.Fprint(&b, pass.Fset, inner.Type) 81 createDiagnostic(pass, inner.Type.Pos(), inner.Type.End(), b.String()) 82 } 83 // if the outer literal's element type is a pointer type *T 84 // and the element is & of a composite literal of type T, 85 // the inner &T may be omitted. 86 if ptr, ok := astType.(*ast.StarExpr); ok { 87 if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND { 88 if inner, ok := addr.X.(*ast.CompositeLit); ok { 89 if match(reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) { 90 var b bytes.Buffer 91 printer.Fprint(&b, pass.Fset, inner.Type) 92 // Account for the & by subtracting 1 from typ.Pos(). 93 createDiagnostic(pass, inner.Type.Pos()-1, inner.Type.End(), "&"+b.String()) 94 } 95 } 96 } 97 } 98 } 99 100 func createDiagnostic(pass *analysis.Pass, start, end token.Pos, typ string) { 101 pass.Report(analysis.Diagnostic{ 102 Pos: start, 103 End: end, 104 Message: "redundant type from array, slice, or map composite literal", 105 SuggestedFixes: []analysis.SuggestedFix{{ 106 Message: fmt.Sprintf("Remove '%s'", typ), 107 TextEdits: []analysis.TextEdit{{ 108 Pos: start, 109 End: end, 110 NewText: []byte{}, 111 }}, 112 }}, 113 }) 114 } 115 116 // match reports whether pattern matches val, 117 // recording wildcard submatches in m. 118 // If m == nil, match checks whether pattern == val. 119 // from https://github.com/golang/go/blob/26154f31ad6c801d8bad5ef58df1e9263c6beec7/src/cmd/gofmt/rewrite.go#L160 120 func match(pattern, val reflect.Value) bool { 121 // Otherwise, pattern and val must match recursively. 122 if !pattern.IsValid() || !val.IsValid() { 123 return !pattern.IsValid() && !val.IsValid() 124 } 125 if pattern.Type() != val.Type() { 126 return false 127 } 128 129 // Special cases. 130 switch pattern.Type() { 131 case identType: 132 // For identifiers, only the names need to match 133 // (and none of the other *ast.Object information). 134 // This is a common case, handle it all here instead 135 // of recursing down any further via reflection. 136 p := pattern.Interface().(*ast.Ident) 137 v := val.Interface().(*ast.Ident) 138 return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name 139 case objectPtrType, positionType: 140 // object pointers and token positions always match 141 return true 142 case callExprType: 143 // For calls, the Ellipsis fields (token.Position) must 144 // match since that is how f(x) and f(x...) are different. 145 // Check them here but fall through for the remaining fields. 146 p := pattern.Interface().(*ast.CallExpr) 147 v := val.Interface().(*ast.CallExpr) 148 if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() { 149 return false 150 } 151 } 152 153 p := reflect.Indirect(pattern) 154 v := reflect.Indirect(val) 155 if !p.IsValid() || !v.IsValid() { 156 return !p.IsValid() && !v.IsValid() 157 } 158 159 switch p.Kind() { 160 case reflect.Slice: 161 if p.Len() != v.Len() { 162 return false 163 } 164 for i := 0; i < p.Len(); i++ { 165 if !match(p.Index(i), v.Index(i)) { 166 return false 167 } 168 } 169 return true 170 171 case reflect.Struct: 172 for i := 0; i < p.NumField(); i++ { 173 if !match(p.Field(i), v.Field(i)) { 174 return false 175 } 176 } 177 return true 178 179 case reflect.Interface: 180 return match(p.Elem(), v.Elem()) 181 } 182 183 // Handle token integers, etc. 184 return p.Interface() == v.Interface() 185 } 186 187 // Values/types for special cases. 188 var ( 189 identType = reflect.TypeOf((*ast.Ident)(nil)) 190 objectPtrType = reflect.TypeOf((*ast.Object)(nil)) 191 positionType = reflect.TypeOf(token.NoPos) 192 callExprType = reflect.TypeOf((*ast.CallExpr)(nil)) 193 )