github.com/julianthome/gore@v0.0.0-20231109011145-b3a6bbe6fe55/quickfix.go (about) 1 package gore 2 3 import ( 4 "go/ast" 5 "go/token" 6 "go/types" 7 "strings" 8 9 "golang.org/x/tools/go/ast/astutil" 10 11 "github.com/motemen/go-quickfix" 12 ) 13 14 // doQuickFix tries to fix the source AST so that it compiles well. 15 func (s *Session) doQuickFix() { 16 const maxAttempts = 10 17 18 if err := s.reset(); err != nil { 19 debugf("reset :: err = %s", err) 20 } 21 22 L: 23 for i := 0; i < maxAttempts; i++ { 24 s.typeInfo = types.Info{ 25 Types: make(map[ast.Expr]types.TypeAndValue), 26 } 27 28 config := quickfix.Config{ 29 Fset: s.fset, 30 Files: append(s.extraFiles, s.file), 31 TypeInfo: &s.typeInfo, 32 Dir: s.tempDir, 33 } 34 _, err := config.QuickFixOnce() 35 if err == nil { 36 break 37 } 38 39 debugf("quickFix :: err = %s", err) 40 41 errList, ok := err.(quickfix.ErrorList) 42 if !ok { 43 continue 44 } 45 46 // (try to) fix gore-specific remaining errors 47 for _, err := range errList { 48 err, ok := err.(types.Error) 49 if !ok { 50 continue 51 } 52 53 // "... used as value": 54 // 55 // convert 56 // __gore_pp(funcWithSideEffectReturningNoValue()) 57 // to 58 // funcWithSideEffectReturningNoValue() 59 if strings.HasSuffix(err.Msg, " used as value") { 60 nodepath, _ := astutil.PathEnclosingInterval(s.file, err.Pos, err.Pos) 61 62 for _, node := range nodepath { 63 stmt, ok := node.(ast.Stmt) 64 if !ok { 65 continue 66 } 67 68 for i := range s.mainBody.List { 69 if s.mainBody.List[i] != stmt { 70 continue 71 } 72 73 exprs := printedExprs(stmt) 74 75 stmts := s.mainBody.List[0:i] 76 for _, expr := range exprs { 77 stmts = append(stmts, &ast.ExprStmt{X: expr}) 78 } 79 80 s.mainBody.List = append(stmts, s.mainBody.List[i+1:]...) 81 continue L 82 } 83 } 84 } 85 } 86 87 debugf("quickFix :: give up: %s", err) 88 break 89 } 90 } 91 92 func (s *Session) clearQuickFix() { 93 // make all import specs explicit (i.e. no "_"). 94 for _, imp := range s.file.Imports { 95 imp.Name = nil 96 } 97 98 for i := 0; i < len(s.mainBody.List); { 99 stmt := s.mainBody.List[i] 100 101 // remove assignment statement if it is omittable. 102 if assign, ok := stmt.(*ast.AssignStmt); ok && s.isPureAssignStmt(assign) { 103 s.mainBody.List = append(s.mainBody.List[0:i], s.mainBody.List[i+1:]...) 104 continue 105 } 106 107 // remove expressions just for printing out 108 // i.e. what causes "evaluated but not used." 109 if exprs := printedExprs(stmt); exprs != nil { 110 allPure := true 111 for _, expr := range exprs { 112 if !s.isPureExpr(expr) { 113 allPure = false 114 break 115 } 116 } 117 118 if allPure { 119 s.mainBody.List = append(s.mainBody.List[0:i], s.mainBody.List[i+1:]...) 120 continue 121 } 122 123 // convert possibly impure expressions to blank assignment 124 var trailing []ast.Stmt 125 s.mainBody.List, trailing = s.mainBody.List[0:i], s.mainBody.List[i+1:] 126 for _, expr := range exprs { 127 if !s.isPureExpr(expr) { 128 t := s.typeInfo.TypeOf(expr) 129 var lhs []ast.Expr 130 if t, ok := t.(*types.Tuple); ok { 131 lhs = make([]ast.Expr, t.Len()) 132 for i := 0; i < t.Len(); i++ { 133 lhs[i] = ast.NewIdent("_") 134 } 135 } else { 136 lhs = []ast.Expr{ast.NewIdent("_")} 137 } 138 s.mainBody.List = append(s.mainBody.List, &ast.AssignStmt{ 139 Lhs: lhs, Tok: token.ASSIGN, Rhs: []ast.Expr{expr}, 140 }) 141 } 142 } 143 144 s.mainBody.List = append(s.mainBody.List, trailing...) 145 continue 146 } 147 148 i++ 149 } 150 151 debugf("clearQuickFix :: %s", showNode(s.fset, s.mainBody)) 152 } 153 154 // isPureAssignStmt returns assignment is pure and omittable. 155 func (s *Session) isPureAssignStmt(stmt *ast.AssignStmt) bool { 156 for _, lhs := range stmt.Lhs { 157 if !isNamedIdent(lhs, "_") { 158 return false 159 } 160 } 161 for _, expr := range stmt.Rhs { 162 if !s.isPureExpr(expr) { 163 return false 164 } 165 } 166 return true 167 } 168 169 // printedExprs returns arguments of statement stmt of form "p(x...)" 170 func printedExprs(stmt ast.Stmt) []ast.Expr { 171 st, ok := stmt.(*ast.ExprStmt) 172 if !ok { 173 return nil 174 } 175 176 // first check whether the expr is p(_) form 177 call, ok := st.X.(*ast.CallExpr) 178 if !ok { 179 return nil 180 } 181 182 if !isNamedIdent(call.Fun, printerName) { 183 return nil 184 } 185 186 return call.Args 187 } 188 189 var pureBuiltinFuncNames = map[string]bool{ 190 "append": true, 191 "cap": true, 192 "complex": true, 193 "imag": true, 194 "len": true, 195 "make": true, 196 "new": true, 197 "real": true, 198 } 199 200 // isPureExpr checks if an expression expr is "pure", which means 201 // removing this expression will no affect the entire program. 202 // - identifiers ("x") 203 // - types 204 // - selectors ("x.y") 205 // - slices ("a[n:m]") 206 // - literals ("1") 207 // - type conversion ("int(1)") 208 // - type assertion ("x.(int)") 209 // - call of some built-in functions as listed in pureBuiltinFuncNames 210 func (s *Session) isPureExpr(expr ast.Expr) bool { 211 if expr == nil { 212 return true 213 } 214 215 switch expr := expr.(type) { 216 case *ast.Ident: 217 return true 218 case *ast.BasicLit: 219 return true 220 case *ast.BinaryExpr: 221 return s.isPureExpr(expr.X) && s.isPureExpr(expr.Y) 222 case *ast.CallExpr: 223 tv := s.typeInfo.Types[expr.Fun] 224 for _, arg := range expr.Args { 225 if !s.isPureExpr(arg) { 226 return false 227 } 228 } 229 230 if tv.IsType() { 231 return true 232 } 233 234 if tv.IsBuiltin() { 235 if ident, ok := expr.Fun.(*ast.Ident); ok { 236 if pureBuiltinFuncNames[ident.Name] { 237 return true 238 } 239 } 240 } 241 242 return false 243 case *ast.CompositeLit: 244 return true 245 case *ast.FuncLit: 246 return true 247 case *ast.IndexExpr: 248 return s.isPureExpr(expr.X) && s.isPureExpr(expr.Index) 249 case *ast.SelectorExpr: 250 return s.isPureExpr(expr.X) 251 case *ast.SliceExpr: 252 return s.isPureExpr(expr.Low) && s.isPureExpr(expr.High) && s.isPureExpr(expr.Max) 253 case *ast.StarExpr: 254 return s.isPureExpr(expr.X) 255 case *ast.TypeAssertExpr: 256 return true 257 case *ast.UnaryExpr: 258 return s.isPureExpr(expr.X) 259 case *ast.ParenExpr: 260 return s.isPureExpr(expr.X) 261 262 case *ast.InterfaceType: 263 return true 264 case *ast.ArrayType: 265 return true 266 case *ast.ChanType: 267 return true 268 case *ast.KeyValueExpr: 269 return true 270 case *ast.MapType: 271 return true 272 case *ast.StructType: 273 return true 274 case *ast.FuncType: 275 return true 276 277 case *ast.Ellipsis: 278 return true 279 280 case *ast.BadExpr: 281 return false 282 } 283 284 return false 285 }