github.com/yunabe/lgo@v0.0.0-20190709125917-42c42d410fdf/converter/converter.go (about)

     1  package converter
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"go/ast"
     7  	"go/format"
     8  	"go/importer"
     9  	"go/token"
    10  	"go/types"
    11  	"os/exec"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  
    17  	"github.com/yunabe/lgo/cmd/install"
    18  	"github.com/yunabe/lgo/core" // This is also important to install core package to GOPATH when this package is tested with go test.
    19  	"github.com/yunabe/lgo/parser"
    20  )
    21  
    22  const lgoInitFuncName = "lgo_init"
    23  const lgoPackageName = "lgo_exec" // TODO: Set a proper name.
    24  const runCtxName = "_ctx"
    25  
    26  var lgoImporter = importer.Default()
    27  
    28  // SetLGOImporter sets a global types.Importer used in this package.
    29  // This method is used in cmd/lgo-internal to install missing .a files to the system.
    30  func SetLGOImporter(im types.Importer) {
    31  	lgoImporter = im
    32  }
    33  
    34  // PackageArchiveInstaller is the interface that is used to install .a files of packages
    35  // used in lgo code before static code analysis.
    36  type PackageArchiveInstaller interface {
    37  	Install(pkgs []string) error
    38  }
    39  
    40  var pkgAInstaller PackageArchiveInstaller
    41  
    42  // SetPackageArchiveInstaller sets a global PackageArchiveInstaller used in the current process.
    43  func SetPackageArchiveInstaller(i PackageArchiveInstaller) {
    44  	pkgAInstaller = i
    45  }
    46  
    47  // maybeInstallPackageArchives installs .a files for third-party libraries into LGOPATH.
    48  func maybeInstallPackageArchives(imports []*ast.ImportSpec) {
    49  	if pkgAInstaller == nil {
    50  		return
    51  	}
    52  	pkgs := make([]string, 0, len(imports))
    53  	for _, im := range imports {
    54  		path, err := strconv.Unquote(im.Path.Value)
    55  		if err != nil {
    56  			continue
    57  		}
    58  		if install.IsStdPkg(path) {
    59  			continue
    60  		}
    61  		pkgs = append(pkgs, path)
    62  	}
    63  	if len(pkgs) > 0 {
    64  		pkgAInstaller.Install(pkgs)
    65  	}
    66  }
    67  
    68  // ErrorList is a list of *Errors.
    69  // The zero value for an ErrorList is an empty ErrorList ready to use.
    70  type ErrorList []error
    71  
    72  // Add adds an Error with given position and error message to an ErrorList.
    73  func (p *ErrorList) Add(err error) {
    74  	*p = append(*p, err)
    75  }
    76  
    77  // An ErrorList implements the error interface.
    78  func (p ErrorList) Error() string {
    79  	switch len(p) {
    80  	case 0:
    81  		return "no errors"
    82  	case 1:
    83  		return p[0].Error()
    84  	}
    85  	return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1)
    86  }
    87  
    88  func uniqueSortedNames(ids []*ast.Ident) []string {
    89  	var s []string
    90  	m := make(map[string]bool)
    91  	for _, id := range ids {
    92  		if m[id.Name] || id.Name == "_" {
    93  			continue
    94  		}
    95  		m[id.Name] = true
    96  		s = append(s, id.Name)
    97  	}
    98  	sort.Sort(sort.StringSlice(s))
    99  	return s
   100  }
   101  
   102  func parseLesserGoString(src string) (*token.FileSet, *parser.LGOBlock, error) {
   103  	fset := token.NewFileSet()
   104  	f, err := parser.ParseLesserGoFile(fset, "", src, parser.ParseComments)
   105  	return fset, f, err
   106  }
   107  
   108  type phase1Out struct {
   109  	vars       []*ast.Ident
   110  	initFunc   *ast.FuncDecl
   111  	file       *ast.File
   112  	consumeAll *ast.AssignStmt
   113  
   114  	// The last expression of lgo if exists. This expression will be rewritten later
   115  	// to print the last expression.
   116  	// If the last expression is not a function call, the expression is wrapped with panic
   117  	// and lastExprWrapped is set to true.
   118  	lastExpr        *ast.ExprStmt
   119  	lastExprWrapped bool
   120  }
   121  
   122  func convertToPhase1(blk *parser.LGOBlock) (out phase1Out) {
   123  	var decls []ast.Decl
   124  	var initBody []ast.Stmt
   125  	for _, stmt := range blk.Stmts {
   126  		if decl, ok := stmt.(*ast.DeclStmt); ok {
   127  			if gen, ok := decl.Decl.(*ast.GenDecl); ok {
   128  				if gen.Tok == token.CONST || gen.Tok == token.VAR {
   129  					initBody = append(initBody, stmt)
   130  					if gen.Tok == token.VAR {
   131  						for _, spec := range gen.Specs {
   132  							spec := spec.(*ast.ValueSpec)
   133  							for _, indent := range spec.Names {
   134  								out.vars = append(out.vars, indent)
   135  							}
   136  						}
   137  					}
   138  					continue
   139  				}
   140  			}
   141  			decls = append(decls, decl.Decl)
   142  			continue
   143  		}
   144  		initBody = append(initBody, stmt)
   145  		if assign, ok := stmt.(*ast.AssignStmt); ok && assign.Tok == token.DEFINE {
   146  			for _, l := range assign.Lhs {
   147  				if ident, ok := l.(*ast.Ident); ok {
   148  					out.vars = append(out.vars, ident)
   149  				}
   150  			}
   151  		}
   152  	}
   153  	if initBody != nil {
   154  		// Handle the last expression.
   155  		last := initBody[len(initBody)-1]
   156  		if es, ok := last.(*ast.ExprStmt); ok {
   157  			out.lastExpr = es
   158  			if _, ok := es.X.(*ast.CallExpr); !ok {
   159  				// If the last expr is not function call, wrap it with panic to avoid "is not used" error.
   160  				// You should not wrap function calls because panic(novalue()) is also invalid in Go.
   161  				es.X = &ast.CallExpr{
   162  					Fun:  ast.NewIdent("panic"),
   163  					Args: []ast.Expr{es.X},
   164  				}
   165  				out.lastExprWrapped = true
   166  			}
   167  		}
   168  	}
   169  
   170  	if out.vars != nil {
   171  		// Create consumeAll.
   172  		if varNames := uniqueSortedNames(out.vars); len(varNames) > 0 {
   173  			var lhs, rhs []ast.Expr
   174  			for _, name := range varNames {
   175  				lhs = append(lhs, &ast.Ident{Name: "_"})
   176  				rhs = append(rhs, &ast.Ident{Name: name})
   177  			}
   178  			out.consumeAll = &ast.AssignStmt{
   179  				Lhs: lhs,
   180  				Rhs: rhs,
   181  				Tok: token.ASSIGN,
   182  			}
   183  			initBody = append(initBody, out.consumeAll)
   184  		}
   185  	}
   186  
   187  	out.initFunc = &ast.FuncDecl{
   188  		Name: ast.NewIdent(lgoInitFuncName),
   189  		Type: &ast.FuncType{},
   190  		Body: &ast.BlockStmt{
   191  			List: initBody,
   192  		},
   193  	}
   194  	decls = append(decls, out.initFunc)
   195  	out.file = &ast.File{
   196  		Package:    token.NoPos,
   197  		Name:       ast.NewIdent(lgoPackageName),
   198  		Decls:      decls,
   199  		Scope:      blk.Scope,
   200  		Imports:    blk.Imports,
   201  		Unresolved: nil,
   202  		Comments:   blk.Comments,
   203  	}
   204  	return
   205  }
   206  
   207  func convertToPhase2(ph1 phase1Out, pkg *types.Package, checker *types.Checker, conf *Config) {
   208  	immg := newImportManager(pkg, ph1.file, checker)
   209  	prependPkgToOlds(conf, checker, ph1.file, immg)
   210  
   211  	var newInitBody []ast.Stmt
   212  	var varSpecs []ast.Spec
   213  	for _, stmt := range ph1.initFunc.Body.List {
   214  		if stmt == ph1.consumeAll {
   215  			continue
   216  		}
   217  		if stmt == ph1.lastExpr {
   218  			var target ast.Expr
   219  			if ph1.lastExprWrapped {
   220  				target = ph1.lastExpr.X.(*ast.CallExpr).Args[0]
   221  			} else if tuple, ok := checker.Types[ph1.lastExpr.X].Type.(*types.Tuple); !ok || tuple.Len() > 0 {
   222  				// "!ok" means single return value.
   223  				target = ph1.lastExpr.X
   224  			}
   225  			if target != nil {
   226  				corePkg, err := lgoImporter.Import(core.SelfPkgPath)
   227  				if err != nil {
   228  					panic(fmt.Sprintf("Failed to import core: %v", err))
   229  				}
   230  
   231  				ph1.lastExpr.X = &ast.CallExpr{
   232  					Fun: &ast.SelectorExpr{
   233  						X:   &ast.Ident{Name: immg.shortName(corePkg)},
   234  						Sel: &ast.Ident{Name: "LgoPrintln"},
   235  					},
   236  					Args: []ast.Expr{target},
   237  				}
   238  			}
   239  		}
   240  		if decl, ok := stmt.(*ast.DeclStmt); ok {
   241  			gen := decl.Decl.(*ast.GenDecl)
   242  			if gen.Tok == token.VAR {
   243  				for _, spec := range gen.Specs {
   244  					spec := spec.(*ast.ValueSpec)
   245  					for i, name := range spec.Names {
   246  						if i == 0 && spec.Type != nil {
   247  							// Reuses spec.Type so that we can keep original nodes as far as possible.
   248  							// TODO: Reuse spec for all `i` if spec.Type != nil.
   249  							if isValidTypeObject(checker.Defs[name]) {
   250  								varSpecs = append(varSpecs, &ast.ValueSpec{
   251  									Names: []*ast.Ident{name},
   252  									Type:  spec.Type,
   253  								})
   254  							}
   255  							continue
   256  						}
   257  						if vspec := varSpecFromIdent(immg, pkg, name, checker, true); vspec != nil {
   258  							varSpecs = append(varSpecs, vspec)
   259  						}
   260  					}
   261  					if spec.Values != nil {
   262  						var lhs []ast.Expr
   263  						for _, name := range spec.Names {
   264  							lhs = append(lhs, &ast.Ident{Name: name.Name})
   265  						}
   266  						newInitBody = append(newInitBody, &ast.AssignStmt{
   267  							Lhs: lhs,
   268  							Rhs: spec.Values,
   269  							Tok: token.ASSIGN,
   270  						})
   271  					}
   272  				}
   273  			} else if gen.Tok == token.CONST {
   274  				ph1.file.Decls = append(ph1.file.Decls, gen)
   275  			} else {
   276  				panic(fmt.Sprintf("Unexpected token: %v", gen.Tok))
   277  			}
   278  			continue
   279  		}
   280  		newInitBody = append(newInitBody, stmt)
   281  		if assign, ok := stmt.(*ast.AssignStmt); ok && assign.Tok == token.DEFINE {
   282  			// Rewrite := with =.
   283  			assign.Tok = token.ASSIGN
   284  			// Define vars.
   285  			for _, lhs := range assign.Lhs {
   286  				if ident, ok := lhs.(*ast.Ident); ok && ident.Name != "_" {
   287  					if vspec := varSpecFromIdent(immg, pkg, ident, checker, false); vspec != nil {
   288  						varSpecs = append(varSpecs, vspec)
   289  					}
   290  				}
   291  			}
   292  		}
   293  	}
   294  
   295  	if varSpecs != nil {
   296  		ph1.file.Decls = append(ph1.file.Decls, &ast.GenDecl{
   297  			// go/printer prints multiple vars only when Lparen is set.
   298  			Lparen: 1,
   299  			Rparen: 2,
   300  			Tok:    token.VAR,
   301  			Specs:  varSpecs,
   302  		})
   303  	}
   304  	if varSpecs != nil && conf.RegisterVars {
   305  		corePkg, err := lgoImporter.Import(core.SelfPkgPath)
   306  		if err != nil {
   307  			panic(fmt.Sprintf("Failed to import core: %v", err))
   308  		}
   309  		var registers []ast.Stmt
   310  		for _, vs := range varSpecs {
   311  			// TODO: Reconsider varSpecs type.
   312  			for _, name := range vs.(*ast.ValueSpec).Names {
   313  				call := &ast.CallExpr{
   314  					Fun: &ast.SelectorExpr{
   315  						X:   &ast.Ident{Name: immg.shortName(corePkg)},
   316  						Sel: &ast.Ident{Name: "LgoRegisterVar"},
   317  					},
   318  					Args: []ast.Expr{
   319  						&ast.BasicLit{
   320  							Kind:  token.STRING,
   321  							Value: fmt.Sprintf("%q", name),
   322  						},
   323  						&ast.UnaryExpr{
   324  							Op: token.AND,
   325  							X:  ast.NewIdent(name.Name),
   326  						},
   327  					},
   328  				}
   329  				registers = append(registers, &ast.ExprStmt{X: call})
   330  			}
   331  		}
   332  		newInitBody = append(registers, newInitBody...)
   333  	}
   334  	ph1.initFunc.Body.List = newInitBody
   335  
   336  	var newDels []ast.Decl
   337  	for _, im := range immg.injectedImports {
   338  		newDels = append(newDels, im)
   339  	}
   340  	for _, decl := range ph1.file.Decls {
   341  		if newInitBody == nil && decl == ph1.initFunc {
   342  			// Remove initBody if it's empty now.
   343  			continue
   344  		}
   345  		newDels = append(newDels, decl)
   346  	}
   347  	ph1.file.Decls = newDels
   348  }
   349  
   350  type importManager struct {
   351  	checker   *types.Checker
   352  	current   *types.Package
   353  	fileScope *types.Scope
   354  	names     map[*types.Package]string
   355  	counter   int
   356  
   357  	// Outputs
   358  	injectedImports []*ast.GenDecl
   359  }
   360  
   361  func newImportManager(current *types.Package, file *ast.File, checker *types.Checker) *importManager {
   362  	fileScope := checker.Scopes[file]
   363  	names := make(map[*types.Package]string)
   364  	for _, name := range fileScope.Names() {
   365  		obj := fileScope.Lookup(name)
   366  		pname, ok := obj.(*types.PkgName)
   367  		if ok {
   368  			names[pname.Imported()] = name
   369  		}
   370  	}
   371  	return &importManager{
   372  		checker:   checker,
   373  		current:   current,
   374  		fileScope: fileScope,
   375  		names:     names,
   376  		counter:   0,
   377  	}
   378  }
   379  
   380  func (m *importManager) shortName(pkg *types.Package) string {
   381  	if pkg == m.current {
   382  		return ""
   383  	}
   384  	n, ok := m.names[pkg]
   385  	if ok {
   386  		return n
   387  	}
   388  	for {
   389  		n = fmt.Sprintf("pkg%d", m.counter)
   390  		m.counter++
   391  		if _, obj := m.fileScope.LookupParent(n, token.NoPos); obj == nil {
   392  			break
   393  		}
   394  		// name conflict. Let's continue.
   395  	}
   396  	m.names[pkg] = n
   397  	m.injectedImports = append(m.injectedImports, &ast.GenDecl{
   398  		Tok: token.IMPORT,
   399  		Specs: []ast.Spec{
   400  			&ast.ImportSpec{
   401  				Name: ast.NewIdent(n),
   402  				Path: &ast.BasicLit{
   403  					Kind:  token.STRING,
   404  					Value: fmt.Sprintf("%q", pkg.Path()),
   405  				},
   406  			},
   407  		},
   408  	})
   409  	return n
   410  }
   411  
   412  // Returns false if obj == nil or the type of obj is types.Invalid.
   413  func isValidTypeObject(obj types.Object) bool {
   414  	if obj == nil {
   415  		return false
   416  	}
   417  	if basic, ok := obj.Type().(*types.Basic); ok && basic.Kind() == types.Invalid {
   418  		return false
   419  	}
   420  	return true
   421  }
   422  
   423  // If reuseIdent is true, varSpecFromIdent reuses id in the return value. Otherwise, varSpecFromIdent uses a new Ident inside the return value.
   424  func varSpecFromIdent(immg *importManager, pkg *types.Package, ident *ast.Ident, checker *types.Checker,
   425  	reuseIdent bool) *ast.ValueSpec {
   426  	obj := checker.Defs[ident]
   427  	if obj == nil {
   428  		return nil
   429  	}
   430  	if !isValidTypeObject(obj) {
   431  		// This check is important when convertToPhase2 is called from inspectObject.
   432  		return nil
   433  	}
   434  	typStr := types.TypeString(obj.Type(), func(pkg *types.Package) string {
   435  		return immg.shortName(pkg)
   436  	})
   437  	typExr, err := parser.ParseExpr(typStr)
   438  	if err != nil {
   439  		panic(fmt.Sprintf("Failed to parse type expr %q: %v", typStr, err))
   440  	}
   441  	if !reuseIdent {
   442  		ident = &ast.Ident{Name: ident.Name}
   443  	}
   444  	return &ast.ValueSpec{
   445  		Names: []*ast.Ident{ident},
   446  		Type:  typExr,
   447  	}
   448  }
   449  
   450  // A Config node controls the spec of Convert function.
   451  type Config struct {
   452  	Olds         []types.Object
   453  	OldImports   []*types.PkgName
   454  	DefPrefix    string
   455  	RefPrefix    string
   456  	LgoPkgPath   string
   457  	AutoExitCode bool
   458  	RegisterVars bool
   459  }
   460  
   461  // A ConvertResult is a result of code conversion by Convert.
   462  type ConvertResult struct {
   463  	Src     string
   464  	Pkg     *types.Package
   465  	Checker *types.Checker
   466  	Imports []*types.PkgName
   467  	// A list of package paths imported in the final Src.
   468  	FinalDeps []string
   469  
   470  	Err error
   471  }
   472  
   473  // findIdentWithPos finds an ast.Ident node at pos. Returns nil if pos does not point an Ident.
   474  // findIdentWithPos returns an identifier if pos points the identifier (start <= pos < end) or pos is right after the identifier (pos == end).
   475  func findIdentWithPos(node ast.Node, pos token.Pos) *ast.Ident {
   476  	v := &findIdentVisitor{pos: pos}
   477  	ast.Walk(v, node)
   478  	return v.ident
   479  }
   480  
   481  type findIdentVisitor struct {
   482  	skipRoot ast.Node
   483  	pos      token.Pos
   484  	ident    *ast.Ident
   485  }
   486  
   487  func (v *findIdentVisitor) Visit(node ast.Node) ast.Visitor {
   488  	if node == nil || v.ident != nil {
   489  		return nil
   490  	}
   491  	if node == v.skipRoot {
   492  		return v
   493  	}
   494  	if v.pos < node.Pos() || node.End() < v.pos {
   495  		return nil
   496  	}
   497  	if id, ok := node.(*ast.Ident); ok {
   498  		v.ident = id
   499  		return nil
   500  	}
   501  	if call, ok := node.(*ast.CallExpr); ok {
   502  		// Special handling for CallExpr to show docs of functions while users are typing args.
   503  		// See TestInspect/func_args test cases.
   504  		cv := findIdentVisitor{skipRoot: node, pos: v.pos}
   505  		ast.Walk(&cv, node)
   506  		if cv.ident != nil {
   507  			v.ident = cv.ident
   508  			return nil
   509  		}
   510  		if v.pos < call.Lparen || call.Rparen < v.pos {
   511  			return nil
   512  		}
   513  		fun := call.Fun
   514  		if sel, ok := fun.(*ast.SelectorExpr); ok {
   515  			fun = sel.Sel
   516  		}
   517  		if id, ok := fun.(*ast.Ident); ok {
   518  			v.ident = id
   519  		}
   520  		return nil
   521  	}
   522  	return v
   523  }
   524  
   525  // InspectIdent shows a document or a query for go doc command for the identifier at pos.
   526  func InspectIdent(src string, pos token.Pos, conf *Config) (doc, query string) {
   527  	obj, local := inspectObject(src, pos, conf)
   528  	if obj == nil {
   529  		return
   530  	}
   531  	doc, q := getDocOrGoDocQuery(obj, local)
   532  	if doc != "" || q == nil {
   533  		return
   534  	}
   535  	if pkg := obj.Pkg(); pkg != nil && pkg.IsLgo {
   536  		// rename unexported identifiers.
   537  		for i, id := range q.ids {
   538  			if len(id) == 0 {
   539  				continue
   540  			}
   541  			if c := id[0]; c < 'A' || 'Z' < c {
   542  				q.ids[i] = conf.DefPrefix + id
   543  			}
   544  		}
   545  	}
   546  	query = q.pkg + "." + strings.Join(q.ids, ".")
   547  	return
   548  }
   549  
   550  func injectLgoContext(pkg *types.Package, scope *types.Scope) types.Object {
   551  	if scope.Lookup(runCtxName) == nil {
   552  		corePkg, err := lgoImporter.Import(core.SelfPkgPath)
   553  		if err != nil {
   554  			panic(fmt.Sprintf("Failed to import core: %v", err))
   555  		}
   556  		ctx := types.NewVar(token.NoPos, pkg, runCtxName, corePkg.Scope().Lookup("LgoContext").Type())
   557  		scope.Insert(ctx)
   558  		return ctx
   559  	}
   560  	return nil
   561  }
   562  
   563  func inspectObject(src string, pos token.Pos, conf *Config) (obj types.Object, isLocal bool) {
   564  	// TODO: Consolidate code with Convert.
   565  	fset, blk, _ := parseLesserGoString(src)
   566  	var target *ast.Ident
   567  	for _, stmt := range blk.Stmts {
   568  		if id := findIdentWithPos(stmt, pos); id != nil {
   569  			target = id
   570  			break
   571  		}
   572  	}
   573  	if target == nil {
   574  		return nil, false
   575  	}
   576  	phase1 := convertToPhase1(blk)
   577  
   578  	makePkg := func() *types.Package {
   579  		// TODO: Add a proper name to the package though it's not used at this moment.
   580  		pkg, vscope := types.NewPackageWithOldValues("cmd/hello", "", conf.Olds)
   581  		pkg.IsLgo = true
   582  		// TODO: Come up with better implementation to resolve pkg <--> vscope circular deps.
   583  		for _, im := range conf.OldImports {
   584  			pname := types.NewPkgName(token.NoPos, pkg, im.Name(), im.Imported())
   585  			vscope.Insert(pname)
   586  		}
   587  		injectLgoContext(pkg, vscope)
   588  		return pkg
   589  	}
   590  
   591  	// var errs []error
   592  	chConf := &types.Config{
   593  		Importer: lgoImporter,
   594  		Error: func(err error) {
   595  			//	errs = append(errs, err)
   596  		},
   597  		IgnoreFuncBodies:  true,
   598  		DontIgnoreLgoInit: true,
   599  	}
   600  	var info = types.Info{
   601  		Defs:   make(map[*ast.Ident]types.Object),
   602  		Uses:   make(map[*ast.Ident]types.Object),
   603  		Scopes: make(map[ast.Node]*types.Scope),
   604  		Types:  make(map[ast.Expr]types.TypeAndValue),
   605  	}
   606  	pkg := makePkg()
   607  	checker := types.NewChecker(chConf, fset, pkg, &info)
   608  	checker.Files([]*ast.File{phase1.file})
   609  
   610  	convertToPhase2(phase1, pkg, checker, conf)
   611  	{
   612  		chConf := &types.Config{
   613  			Importer: newImporterWithOlds(conf.Olds),
   614  			Error: func(err error) {
   615  				// Ignore errors.
   616  				// It is necessary to set this noop func because checker stops analyzing code
   617  				// when the first error is found if Error is nil.
   618  			},
   619  			IgnoreFuncBodies:  false,
   620  			DontIgnoreLgoInit: true,
   621  		}
   622  		var info = types.Info{
   623  			Defs:   make(map[*ast.Ident]types.Object),
   624  			Uses:   make(map[*ast.Ident]types.Object),
   625  			Scopes: make(map[ast.Node]*types.Scope),
   626  			Types:  make(map[ast.Expr]types.TypeAndValue),
   627  		}
   628  		// Note: Do not reuse pkg above here because variables are already defined in the scope of pkg above.
   629  		pkg := makePkg()
   630  		checker := types.NewChecker(chConf, fset, pkg, &info)
   631  		checker.Files([]*ast.File{phase1.file})
   632  		var obj types.Object
   633  		obj = checker.Uses[target]
   634  		if obj == nil {
   635  			obj = checker.Defs[target]
   636  		}
   637  		if obj == nil {
   638  			return nil, false
   639  		}
   640  		return obj, obj.Pkg() == pkg
   641  	}
   642  }
   643  
   644  type goDocQuery struct {
   645  	pkg string
   646  	ids []string
   647  }
   648  
   649  func getPkgPath(pkg *types.Package) string {
   650  	if pkg != nil {
   651  		return pkg.Path()
   652  	}
   653  	return "builtin"
   654  }
   655  
   656  var onceDocSupportField sync.Once
   657  var docSupportField bool
   658  
   659  func isFieldDocSupported() bool {
   660  	// go doc of go1.8 does not support struct fields.
   661  	onceDocSupportField.Do(func() {
   662  		if err := exec.Command("go", "doc", "flag", "Flag.Name").Run(); err == nil {
   663  			docSupportField = true
   664  		}
   665  	})
   666  	return docSupportField
   667  }
   668  
   669  // getDocOrGoDocQuery returns a doc string for obj or a query to retrieve a document with go doc (An argument of go doc command).
   670  // getDocOrGoDocQuery returns ("", "") if we do not show anything for obj.
   671  func getDocOrGoDocQuery(obj types.Object, isLocal bool) (doc string, query *goDocQuery) {
   672  	if pkg, _ := obj.(*types.PkgName); pkg != nil {
   673  		query = &goDocQuery{pkg.Imported().Path(), nil}
   674  		return
   675  	}
   676  	if fn, _ := obj.(*types.Func); fn != nil {
   677  		if isLocal {
   678  			// TODO: Print the receiver.
   679  			var buf bytes.Buffer
   680  			buf.WriteString("func " + fn.Name())
   681  			types.WriteSignature(&buf, fn.Type().(*types.Signature), nil)
   682  			doc = buf.String()
   683  			return
   684  		}
   685  		sig := fn.Type().(*types.Signature)
   686  		recv := sig.Recv()
   687  		if recv == nil {
   688  			query = &goDocQuery{getPkgPath(fn.Pkg()), []string{fn.Name()}}
   689  			return
   690  		}
   691  		var recvName string
   692  		switch recv := recv.Type().(type) {
   693  		case *types.Named:
   694  			recvName = recv.Obj().Name()
   695  		case *types.Pointer:
   696  			recvName = recv.Elem().(*types.Named).Obj().Name()
   697  		case *types.Interface:
   698  			recvName = func() string {
   699  				if fn.Pkg() == nil {
   700  					return ""
   701  				}
   702  				scope := fn.Pkg().Scope()
   703  				if scope == nil {
   704  					return ""
   705  				}
   706  				for _, name := range scope.Names() {
   707  					if tyn, _ := scope.Lookup(name).(*types.TypeName); tyn != nil {
   708  						if named, _ := tyn.Type().(*types.Named); named != nil {
   709  							if named.Underlying() == recv {
   710  								return name
   711  							}
   712  						}
   713  					}
   714  				}
   715  				return ""
   716  			}()
   717  		default:
   718  			panic(fmt.Errorf("Unexpected receiver type: %#v", recv))
   719  		}
   720  		if recvName != "" {
   721  			query = &goDocQuery{getPkgPath(fn.Pkg()), []string{recvName, fn.Name()}}
   722  		}
   723  		return
   724  	}
   725  	if v, _ := obj.(*types.Var); v != nil {
   726  		if v.IsField() {
   727  			if isLocal {
   728  				// TODO: Print the information of the struct.
   729  				doc = "var " + v.Name() + " " + v.Type().String()
   730  				return
   731  			}
   732  			scope := v.Pkg().Scope()
   733  			for _, name := range scope.Names() {
   734  				tyn, ok := scope.Lookup(name).(*types.TypeName)
   735  				if !ok {
   736  					continue
   737  				}
   738  				st, ok := tyn.Type().Underlying().(*types.Struct)
   739  				if !ok {
   740  					continue
   741  				}
   742  				for i := 0; i < st.NumFields(); i++ {
   743  					f := st.Field(i)
   744  					if f == v {
   745  						if isFieldDocSupported() {
   746  							query = &goDocQuery{getPkgPath(v.Pkg()), []string{tyn.Name(), v.Name()}}
   747  						} else {
   748  							query = &goDocQuery{getPkgPath(v.Pkg()), []string{tyn.Name()}}
   749  						}
   750  						return
   751  					}
   752  				}
   753  			}
   754  			// Not found. This path is tested in TestInspectUnnamedStruct.
   755  			return
   756  		}
   757  		if isLocal {
   758  			// Do not use v.String() because we do not want to print the package path here.
   759  			doc = "var " + v.Name() + " " + v.Type().String()
   760  			return
   761  		}
   762  		query = &goDocQuery{getPkgPath(v.Pkg()), []string{v.Name()}}
   763  		return
   764  	}
   765  	if c, _ := obj.(*types.Const); c != nil {
   766  		if isLocal {
   767  			doc = "const " + c.Name() + " " + c.Type().String()
   768  			return
   769  		}
   770  		query = &goDocQuery{getPkgPath(c.Pkg()), []string{c.Name()}}
   771  	}
   772  	if tyn, _ := obj.(*types.TypeName); tyn != nil {
   773  		if isLocal {
   774  			doc = "type " + tyn.Name() + " " + tyn.Type().Underlying().String()
   775  			return
   776  		}
   777  		// Note: Use getPkgPath here because tyn.Pkg() is nil for built-in types like float64.
   778  		query = &goDocQuery{getPkgPath(tyn.Pkg()), []string{tyn.Name()}}
   779  		return
   780  	}
   781  	if bi, _ := obj.(*types.Builtin); bi != nil {
   782  		query = &goDocQuery{"builtin", []string{bi.Name()}}
   783  		return
   784  	}
   785  	return
   786  }
   787  
   788  // Convert converts a lgo source to a valid Go source.
   789  func Convert(src string, conf *Config) *ConvertResult {
   790  	fset, blk, err := parseLesserGoString(src)
   791  	if err != nil {
   792  		return &ConvertResult{Err: err}
   793  	}
   794  	maybeInstallPackageArchives(blk.Imports)
   795  	phase1 := convertToPhase1(blk)
   796  
   797  	// TODO: Add a proper name to the package though it's not used at this moment.
   798  	pkg, vscope := types.NewPackageWithOldValues("cmd/hello", "", conf.Olds)
   799  	pkg.IsLgo = true
   800  	// TODO: Come up with better implementation to resolve pkg <--> vscope circular deps.
   801  	for _, im := range conf.OldImports {
   802  		pname := types.NewPkgName(token.NoPos, pkg, im.Name(), im.Imported())
   803  		vscope.Insert(pname)
   804  	}
   805  	injectLgoContext(pkg, vscope)
   806  
   807  	var errs []error
   808  	chConf := &types.Config{
   809  		Importer: lgoImporter,
   810  		Error: func(err error) {
   811  			errs = append(errs, err)
   812  		},
   813  		IgnoreFuncBodies:  true,
   814  		DontIgnoreLgoInit: true,
   815  	}
   816  	var info = types.Info{
   817  		Defs:   make(map[*ast.Ident]types.Object),
   818  		Uses:   make(map[*ast.Ident]types.Object),
   819  		Scopes: make(map[ast.Node]*types.Scope),
   820  		Types:  make(map[ast.Expr]types.TypeAndValue),
   821  	}
   822  	checker := types.NewChecker(chConf, fset, pkg, &info)
   823  	checker.Files([]*ast.File{phase1.file})
   824  	if len(errs) > 0 {
   825  		var err error
   826  		if len(errs) > 1 {
   827  			err = ErrorList(errs)
   828  		} else {
   829  			err = errs[0]
   830  		}
   831  		return &ConvertResult{Err: err}
   832  	}
   833  	convertToPhase2(phase1, pkg, checker, conf)
   834  
   835  	fsrc, fpkg, fcheck, finalDeps, err := finalCheckAndRename(phase1.file, fset, conf)
   836  	if err != nil {
   837  		return &ConvertResult{Err: err}
   838  	}
   839  
   840  	var imports []*types.PkgName
   841  	fscope := checker.Scopes[phase1.file]
   842  	for _, name := range fscope.Names() {
   843  		obj := fscope.Lookup(name)
   844  		if pname, ok := obj.(*types.PkgName); ok {
   845  			imports = append(imports, pname)
   846  		}
   847  	}
   848  
   849  	return &ConvertResult{
   850  		Src:       fsrc,
   851  		Pkg:       fpkg,
   852  		Checker:   fcheck,
   853  		Imports:   imports,
   854  		FinalDeps: finalDeps,
   855  	}
   856  }
   857  
   858  type importerWithOlds struct {
   859  	olds map[string]*types.Package
   860  }
   861  
   862  func newImporterWithOlds(olds []types.Object) *importerWithOlds {
   863  	m := make(map[string]*types.Package)
   864  	for _, old := range olds {
   865  		m[old.Pkg().Path()] = old.Pkg()
   866  	}
   867  	return &importerWithOlds{m}
   868  }
   869  
   870  func (im *importerWithOlds) Import(path string) (*types.Package, error) {
   871  	if pkg := im.olds[path]; pkg != nil {
   872  		return pkg, nil
   873  	}
   874  	return lgoImporter.Import(path)
   875  }
   876  
   877  // qualifiedIDFinder finds *ast.Ident that is used as "sel" of "pkg.sel".
   878  // The output of this visitor is used not to rename "pkg.sel" to "pkg.pkg.sel".
   879  // This logic is required for prependPkgToOlds in finalCheckAndRename.
   880  //
   881  // This mechnism is important because the first prependPkgToOlds (at the top of convertToPhase2) is
   882  // also necessary to handle `x := x * x` in TestConvert_twoLgo2.
   883  type qualifiedIDFinder struct {
   884  	checker     *types.Checker
   885  	qualifiedID map[*ast.Ident]bool
   886  }
   887  
   888  func (f *qualifiedIDFinder) Visit(node ast.Node) (w ast.Visitor) {
   889  	sel, _ := node.(*ast.SelectorExpr)
   890  	if sel == nil {
   891  		return f
   892  	}
   893  	x, _ := sel.X.(*ast.Ident)
   894  	if x == nil {
   895  		return f
   896  	}
   897  	pname, _ := f.checker.Uses[x].(*types.PkgName)
   898  	if pname == nil {
   899  		return f
   900  	}
   901  	f.qualifiedID[sel.Sel] = true
   902  	return f
   903  }
   904  
   905  func prependPkgToOlds(conf *Config, checker *types.Checker, file *ast.File, immg *importManager) {
   906  	// Add package names to identities that refers to old values.
   907  	isOld := make(map[types.Object]bool)
   908  	for _, old := range conf.Olds {
   909  		isOld[old] = true
   910  	}
   911  	qif := &qualifiedIDFinder{
   912  		checker:     checker,
   913  		qualifiedID: make(map[*ast.Ident]bool),
   914  	}
   915  	ast.Walk(qif, file)
   916  	rewriteExpr(file, func(expr ast.Expr) ast.Expr {
   917  		id, ok := expr.(*ast.Ident)
   918  		if !ok {
   919  			return expr
   920  		}
   921  		obj, ok := checker.Uses[id]
   922  		if !ok {
   923  			return expr
   924  		}
   925  		if !isOld[obj] || qif.qualifiedID[id] {
   926  			return expr
   927  		}
   928  		return &ast.SelectorExpr{
   929  			X:   &ast.Ident{Name: immg.shortName(obj.Pkg())},
   930  			Sel: id,
   931  		}
   932  	})
   933  }
   934  
   935  // prependPrefixToID prepends a prefix to the name of ident.
   936  // It prepends the prefix the last element if ident.Name contains "."
   937  func prependPrefixToID(indent *ast.Ident, prefix string) {
   938  	idx := strings.LastIndex(indent.Name, ".")
   939  	if idx == -1 {
   940  		indent.Name = prefix + indent.Name
   941  	} else {
   942  		indent.Name = indent.Name[:idx+1] + prefix + indent.Name[idx+1:]
   943  	}
   944  }
   945  
   946  func checkFileInPhase2(conf *Config, file *ast.File, fset *token.FileSet) (checker *types.Checker, pkg *types.Package, runctx types.Object, oldImports []*types.PkgName, err error) {
   947  	var errs []error
   948  	chConf := &types.Config{
   949  		Importer: newImporterWithOlds(conf.Olds),
   950  		Error: func(err error) {
   951  			errs = append(errs, err)
   952  		},
   953  		DisableUnusedImportCheck: true,
   954  	}
   955  	pkg, vscope := types.NewPackageWithOldValues(conf.LgoPkgPath, "", conf.Olds)
   956  	pkg.IsLgo = true
   957  	// TODO: Come up with better implementation to resolve pkg <--> vscope circular deps.
   958  	for _, im := range conf.OldImports {
   959  		pname := types.NewPkgName(token.NoPos, pkg, im.Name(), im.Imported())
   960  		vscope.Insert(pname)
   961  		oldImports = append(oldImports, pname)
   962  	}
   963  	runctx = injectLgoContext(pkg, vscope)
   964  	info := &types.Info{
   965  		Defs:      make(map[*ast.Ident]types.Object),
   966  		Uses:      make(map[*ast.Ident]types.Object),
   967  		Scopes:    make(map[ast.Node]*types.Scope),
   968  		Implicits: make(map[ast.Node]types.Object),
   969  		Types:     make(map[ast.Expr]types.TypeAndValue),
   970  	}
   971  	checker = types.NewChecker(chConf, fset, pkg, info)
   972  	checker.Files([]*ast.File{file})
   973  	if errs != nil {
   974  		// TODO: Return all errors.
   975  		err = errs[0]
   976  		return
   977  	}
   978  	return
   979  }
   980  
   981  // workaroundGoBug22998 imports packages that define methods used in the current package indirectly.
   982  // See https://github.com/yunabe/lgo/issues/11 for details.
   983  func workaroundGoBug22998(decls []ast.Decl, pkg *types.Package, checker *types.Checker) []ast.Decl {
   984  	paths := make(map[string]bool)
   985  	for _, decl := range decls {
   986  		gen, ok := decl.(*ast.GenDecl)
   987  		if !ok || gen.Tok != token.IMPORT {
   988  			continue
   989  		}
   990  		for _, spec := range gen.Specs {
   991  			if path, err := strconv.Unquote(spec.(*ast.ImportSpec).Path.Value); err == nil {
   992  				paths[path] = true
   993  			}
   994  		}
   995  	}
   996  	var targets []string
   997  	for _, obj := range checker.Uses {
   998  		f, ok := obj.(*types.Func)
   999  		if !ok {
  1000  			continue
  1001  		}
  1002  		sig, ok := f.Type().(*types.Signature)
  1003  		if !ok {
  1004  			continue
  1005  		}
  1006  		recv := sig.Recv()
  1007  		if recv == nil {
  1008  			// Ignore functions
  1009  			continue
  1010  		}
  1011  		recvPkg := recv.Pkg()
  1012  		if recvPkg == nil || recvPkg == pkg {
  1013  			// Ignore methods defined in the same pkg (recvPkg == pkg) or builtin (recvPkg == nil).
  1014  			continue
  1015  		}
  1016  		if types.IsInterface(recv.Type()) {
  1017  			continue
  1018  		}
  1019  		path := recvPkg.Path()
  1020  		if !paths[path] {
  1021  			targets = append(targets, path)
  1022  			paths[path] = true
  1023  		}
  1024  	}
  1025  	if len(targets) == 0 {
  1026  		return decls
  1027  	}
  1028  	// Make the order of imports stable to make unit tests stable.
  1029  	sort.Strings(targets)
  1030  	var imspecs []ast.Spec
  1031  	for _, target := range targets {
  1032  		imspecs = append(imspecs, &ast.ImportSpec{
  1033  			Name: ast.NewIdent("_"),
  1034  			Path: &ast.BasicLit{
  1035  				Kind:  token.STRING,
  1036  				Value: fmt.Sprintf("%q", target),
  1037  			},
  1038  		})
  1039  	}
  1040  	// Note: ast printer does not print multiple import specs unless lparen is set.
  1041  	var lparen token.Pos
  1042  	if len(imspecs) > 1 {
  1043  		lparen = token.Pos(1)
  1044  	}
  1045  	return append([]ast.Decl{&ast.GenDecl{
  1046  		Tok:    token.IMPORT,
  1047  		Specs:  imspecs,
  1048  		Lparen: lparen,
  1049  	}}, decls...)
  1050  }
  1051  
  1052  func finalCheckAndRename(file *ast.File, fset *token.FileSet, conf *Config) (string, *types.Package, *types.Checker, []string, error) {
  1053  	checker, pkg, runctx, oldImports, err := checkFileInPhase2(conf, file, fset)
  1054  	if err != nil {
  1055  		return "", nil, nil, nil, err
  1056  	}
  1057  	if conf.AutoExitCode {
  1058  		checker, pkg, runctx, oldImports = mayWrapRecvOp(conf, file, fset, checker, pkg, runctx, oldImports)
  1059  	}
  1060  
  1061  	for ident, obj := range checker.Defs {
  1062  		if ast.IsExported(ident.Name) || ident.Name == lgoInitFuncName {
  1063  			continue
  1064  		}
  1065  		if obj == nil {
  1066  			// ident is the top-level package declaration. Skip this.
  1067  			continue
  1068  		}
  1069  		scope := pkg.Scope()
  1070  		if scope != nil && scope.Lookup(obj.Name()) == obj {
  1071  			// Rename package level symbol.
  1072  			ident.Name = conf.DefPrefix + ident.Name
  1073  		} else if _, ok := obj.(*types.Func); ok {
  1074  			// Rename methods.
  1075  			// Notes: *types.Func is top-level func or methods (methods are not necessarily top-level).
  1076  			//        inlined-functions are *types.Var.
  1077  			ident.Name = conf.DefPrefix + ident.Name
  1078  		} else if v, ok := obj.(*types.Var); ok && v.IsField() {
  1079  			ident.Name = conf.DefPrefix + ident.Name
  1080  		}
  1081  	}
  1082  	immg := newImportManager(pkg, file, checker)
  1083  	prependPkgToOlds(conf, checker, file, immg)
  1084  	rewriteExpr(file, func(expr ast.Expr) ast.Expr {
  1085  		// Rewrite _ctx with core.GetExecContext().
  1086  		id, ok := expr.(*ast.Ident)
  1087  		if !ok {
  1088  			return expr
  1089  		}
  1090  		if checker.Uses[id] != runctx {
  1091  			return expr
  1092  		}
  1093  		corePkg, err := lgoImporter.Import(core.SelfPkgPath)
  1094  		if err != nil {
  1095  			panic(fmt.Sprintf("Failed to import core: %v", err))
  1096  		}
  1097  		return &ast.CallExpr{
  1098  			Fun: &ast.SelectorExpr{
  1099  				X:   &ast.Ident{Name: immg.shortName(corePkg)},
  1100  				Sel: &ast.Ident{Name: "GetExecContext"},
  1101  			},
  1102  		}
  1103  	})
  1104  
  1105  	// Inject auto-exit code
  1106  	if conf.AutoExitCode {
  1107  		injectAutoExitToFile(file, immg)
  1108  	}
  1109  	capturePanicInGoRoutine(file, immg, checker)
  1110  
  1111  	// Import lgo packages implicitly referred code inside functions.
  1112  	var newDecls []ast.Decl
  1113  	for _, im := range immg.injectedImports {
  1114  		newDecls = append(newDecls, im)
  1115  	}
  1116  	// Import old imports.
  1117  	for _, im := range oldImports {
  1118  		if !im.Used() {
  1119  			continue
  1120  		}
  1121  		newDecls = append(newDecls, &ast.GenDecl{
  1122  			Tok: token.IMPORT,
  1123  			Specs: []ast.Spec{
  1124  				&ast.ImportSpec{
  1125  					Name: ast.NewIdent(im.Name()),
  1126  					Path: &ast.BasicLit{
  1127  						Kind:  token.STRING,
  1128  						Value: fmt.Sprintf("%q", im.Imported().Path()),
  1129  					},
  1130  				},
  1131  			},
  1132  		})
  1133  	}
  1134  	// Rename unused imports to "_".
  1135  	for _, decl := range file.Decls {
  1136  		gen, ok := decl.(*ast.GenDecl)
  1137  		if !ok || gen.Tok != token.IMPORT {
  1138  			newDecls = append(newDecls, decl)
  1139  			continue
  1140  		}
  1141  		var specs []ast.Spec
  1142  		for _, spec := range gen.Specs {
  1143  			spec := spec.(*ast.ImportSpec)
  1144  			var pname *types.PkgName
  1145  			if spec.Name != nil {
  1146  				pname = checker.Defs[spec.Name].(*types.PkgName)
  1147  			} else {
  1148  				pname = checker.Implicits[spec].(*types.PkgName)
  1149  			}
  1150  			if pname == nil {
  1151  				panic(fmt.Sprintf("*types.PkgName for %v not found", spec))
  1152  			}
  1153  			if !pname.Used() {
  1154  				spec.Name = ast.NewIdent("_")
  1155  			}
  1156  			specs = append(specs, spec)
  1157  		}
  1158  		if specs != nil {
  1159  			gen.Specs = specs
  1160  			newDecls = append(newDecls, gen)
  1161  		}
  1162  	}
  1163  	if len(newDecls) == 0 {
  1164  		// Nothing is left. Return an empty source.
  1165  		return "", pkg, checker, nil, nil
  1166  	}
  1167  	file.Decls = workaroundGoBug22998(newDecls, pkg, checker)
  1168  	for ident, obj := range checker.Uses {
  1169  		if ast.IsExported(ident.Name) {
  1170  			continue
  1171  		}
  1172  		pkg := obj.Pkg()
  1173  		if pkg == nil || !pkg.IsLgo {
  1174  			continue
  1175  		}
  1176  		if pkg.Scope().Lookup(obj.Name()) == obj {
  1177  			// Rename package level symbol.
  1178  			prependPrefixToID(ident, conf.RefPrefix)
  1179  		} else if _, ok := obj.(*types.Func); ok {
  1180  			// Rename methods.
  1181  			prependPrefixToID(ident, conf.RefPrefix)
  1182  		} else if v, ok := obj.(*types.Var); ok && v.IsField() {
  1183  			prependPrefixToID(ident, conf.RefPrefix)
  1184  		}
  1185  	}
  1186  
  1187  	var deps []string
  1188  	for _, decl := range file.Decls {
  1189  		gen, ok := decl.(*ast.GenDecl)
  1190  		if !ok || gen.Tok != token.IMPORT {
  1191  			continue
  1192  		}
  1193  		for _, spec := range gen.Specs {
  1194  			spec := spec.(*ast.ImportSpec)
  1195  			path, err := strconv.Unquote(spec.Path.Value)
  1196  			if err != nil {
  1197  				panic(fmt.Sprintf("invalid path in parsed src: %s", spec.Path.Value))
  1198  			}
  1199  			deps = append(deps, path)
  1200  		}
  1201  	}
  1202  	sort.Strings(deps)
  1203  
  1204  	finalSrc, err := printFinalResult(file, fset)
  1205  	if err != nil {
  1206  		return "", nil, nil, nil, err
  1207  	}
  1208  	return finalSrc, pkg, checker, deps, nil
  1209  }
  1210  
  1211  func capturePanicInGoRoutine(file *ast.File, immg *importManager, checker *types.Checker) {
  1212  	picker := newNamePicker(checker.Defs)
  1213  	ast.Walk(&wrapGoStmtVisitor{immg, picker, checker}, file)
  1214  }
  1215  
  1216  // wrapGoStmtVisitor injects code to wrap go statements.
  1217  //
  1218  // This converts
  1219  // go f(x, y)
  1220  //
  1221  // to
  1222  //
  1223  // {
  1224  //   ctx := InitGoroutine()
  1225  //   fn := f
  1226  //   go func(arg0, arg1 int) {
  1227  //     defer FinalizeGoRoutine(ctx)
  1228  //     fn(arg0, arg1)
  1229  //   }(x, y)
  1230  // }
  1231  // go func() {
  1232  //   defer core.FinalizeGoRoutine(core.InitGoroutine())
  1233  //   f(x, y)
  1234  // }()
  1235  type wrapGoStmtVisitor struct {
  1236  	immg    *importManager
  1237  	picker  *namePicker
  1238  	checker *types.Checker
  1239  }
  1240  
  1241  func (v *wrapGoStmtVisitor) Visit(node ast.Node) ast.Visitor {
  1242  	b, ok := node.(*ast.BlockStmt)
  1243  	if !ok {
  1244  		return v
  1245  	}
  1246  	corePkg, _ := lgoImporter.Import(core.SelfPkgPath)
  1247  	for i, stmt := range b.List {
  1248  		ast.Walk(v, stmt)
  1249  		g, ok := stmt.(*ast.GoStmt)
  1250  		if !ok {
  1251  			continue
  1252  		}
  1253  		// bind func
  1254  		fnName := v.picker.NewName("gofn")
  1255  		body := []ast.Stmt{
  1256  			&ast.AssignStmt{
  1257  				Lhs: []ast.Expr{&ast.Ident{Name: fnName}},
  1258  				Rhs: []ast.Expr{g.Call.Fun},
  1259  				Tok: token.DEFINE,
  1260  			},
  1261  		}
  1262  		// bind arguments
  1263  		var args []ast.Expr
  1264  		for _, arg := range g.Call.Args {
  1265  			argSize := 1
  1266  			if tup, ok := v.checker.Types[arg].Type.(*types.Tuple); ok {
  1267  				argSize = tup.Len()
  1268  			}
  1269  			var lhs []ast.Expr
  1270  			for i := 0; i < argSize; i++ {
  1271  				name := v.picker.NewName("goarg")
  1272  				args = append(args, &ast.Ident{Name: name})
  1273  				lhs = append(lhs, &ast.Ident{Name: name})
  1274  			}
  1275  			body = append(body, &ast.AssignStmt{
  1276  				Lhs: lhs,
  1277  				Rhs: []ast.Expr{arg},
  1278  				Tok: token.DEFINE,
  1279  			})
  1280  		}
  1281  		// Add ectx := InitGoroutine()
  1282  		ectx := v.picker.NewName("ectx")
  1283  		body = append(body, &ast.AssignStmt{
  1284  			Lhs: []ast.Expr{&ast.Ident{Name: ectx}},
  1285  			Rhs: []ast.Expr{&ast.CallExpr{
  1286  				Fun: ast.NewIdent(v.immg.shortName(corePkg) + ".InitGoroutine"),
  1287  			}},
  1288  			Tok: token.DEFINE,
  1289  		})
  1290  		// Add a statement like:
  1291  		// go func() {
  1292  		//   defer FinalizeGoRoutine(ectx)
  1293  		//   gofn(goarg, goarg0, goarg1...)
  1294  		// }
  1295  		body = append(body, &ast.GoStmt{
  1296  			Call: &ast.CallExpr{
  1297  				Fun: &ast.FuncLit{
  1298  					Type: &ast.FuncType{},
  1299  					Body: &ast.BlockStmt{
  1300  						List: []ast.Stmt{
  1301  							&ast.DeferStmt{
  1302  								Call: &ast.CallExpr{
  1303  									Fun: &ast.SelectorExpr{
  1304  										X:   &ast.Ident{Name: v.immg.shortName(corePkg)},
  1305  										Sel: &ast.Ident{Name: "FinalizeGoroutine"},
  1306  									},
  1307  									Args: []ast.Expr{&ast.Ident{Name: ectx}},
  1308  								},
  1309  							},
  1310  							&ast.ExprStmt{X: &ast.CallExpr{
  1311  								Fun:      &ast.Ident{Name: fnName},
  1312  								Args:     args,
  1313  								Ellipsis: g.Call.Ellipsis,
  1314  							}},
  1315  						},
  1316  					},
  1317  				},
  1318  			},
  1319  		})
  1320  		b.List[i] = &ast.BlockStmt{List: body}
  1321  	}
  1322  	// Do not visit children of this node!
  1323  	// We must not visit auto-generated go statements.
  1324  	return nil
  1325  }
  1326  
  1327  // printFinalResult converts the lgo final *ast.File into Go code. This function is almost identical to go/format.Node.
  1328  // This custom function is necessary to handle comments in the first line properly.
  1329  // See the results of "TestConvert_comment.* tests.
  1330  // TODO: We may want to use modified version of go/printer as we do in Format in future.
  1331  func printFinalResult(file *ast.File, fset *token.FileSet) (string, error) {
  1332  	// c.f. func (p *printer) file(src *ast.File) in https://golang.org/src/go/printer/nodes.go
  1333  	var buf bytes.Buffer
  1334  	var err error
  1335  	w := func(s string) {
  1336  		if err == nil {
  1337  			_, err = buf.WriteString(s)
  1338  		}
  1339  	}
  1340  	newLine := func() {
  1341  		if err != nil {
  1342  			return
  1343  		}
  1344  		if b := buf.Bytes(); b[len(b)-1] != '\n' {
  1345  			w("\n")
  1346  		}
  1347  	}
  1348  	w("package ")
  1349  	w(file.Name.Name)
  1350  	w("\n\n")
  1351  	for _, decl := range file.Decls {
  1352  		if err == nil {
  1353  			err = format.Node(&buf, fset, decl)
  1354  			newLine()
  1355  		}
  1356  	}
  1357  	if err != nil {
  1358  		return "", err
  1359  	}
  1360  	if b := buf.Bytes(); b[len(b)-1] != '\n' {
  1361  		w("\n")
  1362  	}
  1363  	return buf.String(), nil
  1364  }