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  }