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 }