func stripParens(x ast.Expr) ast.Expr { if px, strip := x.(*ast.ParenExpr); strip { // parentheses must not be stripped if there are any // unparenthesized composite literals starting with // a type name ast.Inspect(px.X, func(node ast.Node) bool { switch x := node.(type) { case *ast.ParenExpr: // parentheses protect enclosed composite literals return false case *ast.CompositeLit: if isTypeName(x.Type) { strip = false // do not strip parentheses } return false } // in all other cases, keep inspecting return true }) if strip { return stripParens(px.X) } } return x }
// TestIncompleteSelection ensures that an incomplete selector // expression is parsed as a (blank) *ast.SelectorExpr, not a // *ast.BadExpr. func TestIncompleteSelection(t *testing.T) { for _, src := range []string{ "package p; var _ = fmt.", // at EOF "package p; var _ = fmt.\ntype X int", // not at EOF } { fset := token.NewFileSet() f, err := ParseFile(fset, "", src, 0) if err == nil { t.Errorf("ParseFile(%s) succeeded unexpectedly", src) continue } const wantErr = "expected selector or type assertion" if !strings.Contains(err.Error(), wantErr) { t.Errorf("ParseFile returned wrong error %q, want %q", err, wantErr) } var sel *ast.SelectorExpr ast.Inspect(f, func(n ast.Node) bool { if n, ok := n.(*ast.SelectorExpr); ok { sel = n } return true }) if sel == nil { t.Error("found no *ast.SelectorExpr") continue } const wantSel = "&{fmt _}" if fmt.Sprint(sel) != wantSel { t.Errorf("found selector %s, want %s", sel, wantSel) continue } } }
func TestObjects(t *testing.T) { const src = ` package p import fmt "fmt" const pi = 3.14 type T struct{} var x int func f() { L: } ` f, err := ParseFile(token.NewFileSet(), "", src, 0) if err != nil { t.Fatal(err) } objects := map[string]ast.ObjKind{ "p": ast.Bad, // not in a scope "fmt": ast.Bad, // not resolved yet "pi": ast.Con, "T": ast.Typ, "x": ast.Var, "int": ast.Bad, // not resolved yet "f": ast.Fun, "L": ast.Lbl, } ast.Inspect(f, func(n ast.Node) bool { if ident, ok := n.(*ast.Ident); ok { obj := ident.Obj if obj == nil { if objects[ident.Name] != ast.Bad { t.Errorf("no object for %s", ident.Name) } return true } if obj.Name != ident.Name { t.Errorf("names don't match: obj.Name = %s, ident.Name = %s", obj.Name, ident.Name) } kind := objects[ident.Name] if obj.Kind != kind { t.Errorf("%s: obj.Kind = %s; want %s", ident.Name, obj.Kind, kind) } } return true }) }
// TestIssue9979 verifies that empty statements are contained within their enclosing blocks. func TestIssue9979(t *testing.T) { for _, src := range []string{ "package p; func f() {;}", "package p; func f() {L:}", "package p; func f() {L:;}", "package p; func f() {L:\n}", "package p; func f() {L:\n;}", "package p; func f() { ; }", "package p; func f() { L: }", "package p; func f() { L: ; }", "package p; func f() { L: \n}", "package p; func f() { L: \n; }", } { fset := token.NewFileSet() f, err := ParseFile(fset, "", src, 0) if err != nil { t.Fatal(err) } var pos, end token.Pos ast.Inspect(f, func(x ast.Node) bool { switch s := x.(type) { case *ast.BlockStmt: pos, end = s.Pos()+1, s.End()-1 // exclude "{", "}" case *ast.LabeledStmt: pos, end = s.Pos()+2, s.End() // exclude "L:" case *ast.EmptyStmt: // check containment if s.Pos() < pos || s.End() > end { t.Errorf("%s: %T[%d, %d] not inside [%d, %d]", src, s, s.Pos(), s.End(), pos, end) } // check semicolon offs := fset.Position(s.Pos()).Offset if ch := src[offs]; ch != ';' != s.Implicit { want := "want ';'" if s.Implicit { want = "but ';' is implicit" } t.Errorf("%s: found %q at offset %d; %s", src, ch, offs, want) } } return true }) } }
// This example demonstrates how to inspect the AST of a Go program. func ExampleInspect() { // src is the input for which we want to inspect the AST. src := ` package p const c = 1.0 var X = f(3.14)*2 + c ` // Create the AST by parsing src. fset := token.NewFileSet() // positions are relative to fset f, err := parser.ParseFile(fset, "src.go", src, 0) if err != nil { panic(err) } // Inspect the AST and print all identifiers and literals. ast.Inspect(f, func(n ast.Node) bool { var s string switch x := n.(type) { case *ast.BasicLit: s = x.Value case *ast.Ident: s = x.Name } if s != "" { fmt.Printf("%s:\t%s\n", fset.Position(n.Pos()), s) } return true }) // output: // src.go:2:9: p // src.go:3:7: c // src.go:3:11: 1.0 // src.go:4:5: X // src.go:4:9: f // src.go:4:11: 3.14 // src.go:4:17: 2 // src.go:4:21: c }
// playExample synthesizes a new *ast.File based on the provided // file with the provided function body as the body of main. func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { if !strings.HasSuffix(file.Name.Name, "_test") { // We don't support examples that are part of the // greater package (yet). return nil } // Find top-level declarations in the file. topDecls := make(map[*ast.Object]bool) for _, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: topDecls[d.Name.Obj] = true case *ast.GenDecl: for _, spec := range d.Specs { switch s := spec.(type) { case *ast.TypeSpec: topDecls[s.Name.Obj] = true case *ast.ValueSpec: for _, id := range s.Names.List { topDecls[id.Obj] = true } } } } } // Find unresolved identifiers and uses of top-level declarations. unresolved := make(map[string]bool) usesTopDecl := false var inspectFunc func(ast.Node) bool inspectFunc = func(n ast.Node) bool { // For selector expressions, only inspect the left hand side. // (For an expression like fmt.Println, only add "fmt" to the // set of unresolved names, not "Println".) if e, ok := n.(*ast.SelectorExpr); ok { ast.Inspect(e.X, inspectFunc) return false } // For key value expressions, only inspect the value // as the key should be resolved by the type of the // composite literal. if e, ok := n.(*ast.KeyValueExpr); ok { ast.Inspect(e.Value, inspectFunc) return false } if id, ok := n.(*ast.Ident); ok { if id.Obj == nil { unresolved[id.Name] = true } else if topDecls[id.Obj] { usesTopDecl = true } } return true } ast.Inspect(body, inspectFunc) if usesTopDecl { // We don't support examples that are not self-contained (yet). return nil } // Remove predeclared identifiers from unresolved list. for n := range unresolved { if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] { delete(unresolved, n) } } // Use unresolved identifiers to determine the imports used by this // example. The heuristic assumes package names match base import // paths for imports w/o renames (should be good enough most of the time). namedImports := make(map[string]string) // [name]path var blankImports []ast.Spec // _ imports for _, s := range file.Imports { p, err := strconv.Unquote(s.Path.Value) if err != nil { continue } n := path.Base(p) if s.Name != nil { n = s.Name.Name switch n { case "_": blankImports = append(blankImports, s) continue case ".": // We can't resolve dot imports (yet). return nil } } if unresolved[n] { namedImports[n] = p delete(unresolved, n) } } // If there are other unresolved identifiers, give up because this // synthesized file is not going to build. if len(unresolved) > 0 { return nil } // Include documentation belonging to blank imports. var comments []*ast.CommentGroup for _, s := range blankImports { if c := s.(*ast.ImportSpec).Doc; c != nil { comments = append(comments, c) } } // Include comments that are inside the function body. for _, c := range file.Comments { if body.Pos() <= c.Pos() && c.End() <= body.End() { comments = append(comments, c) } } // Strip the "Output:" or "Unordered output:" comment and adjust body // end position. body, comments = stripOutputComment(body, comments) // Synthesize import declaration. importDecl := &ast.GenDecl{ Tok: token.IMPORT, Lparen: 1, // Need non-zero Lparen and Rparen so that printer Rparen: 1, // treats this as a factored import. } for n, p := range namedImports { s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}} if path.Base(p) != n { s.Name = ast.NewIdent(n) } importDecl.Specs = append(importDecl.Specs, s) } importDecl.Specs = append(importDecl.Specs, blankImports...) // Synthesize main function. funcDecl := &ast.FuncDecl{ Name: ast.NewIdent("main"), Type: &ast.FuncType{Params: &ast.FieldList{}}, // FuncType.Params must be non-nil Body: body, } // Synthesize file. return &ast.File{ Name: ast.NewIdent("main"), Decls: []ast.Decl{importDecl, funcDecl}, Comments: comments, } }
func TestResolveIdents(t *testing.T) { testenv.MustHaveGoBuild(t) sources := []string{ ` package p import "fmt" import "math" const pi = math.Pi func sin(x float64) float64 { return math.Sin(x) } var Println = fmt.Println `, ` package p import "fmt" type errorStringer struct { fmt.Stringer; error } func f() string { _ = "foo" return fmt.Sprintf("%d", g()) } func g() (x int) { return } `, ` package p import . "github.com/tcard/sgo/sgo/parser" import "sync" func h() Mode { return ImportsOnly } var _, x int = 1, 2 func init() {} type T struct{ *sync.Mutex; a, b, c int} type I interface{ m() } var _ = T{a: 1, b: 2, c: 3} func (_ T) m() {} func (T) _() {} var i I var _ = i.m func _(s []int) { for i, x := range s { _, _ = i, x } } func _(x interface{}) { switch x := x.(type) { case int: _ = x } switch {} // implicit 'true' tag } `, ` package p type S struct{} func (T) _() {} func (T) _() {} `, ` package p func _() { L0: L1: goto L0 for { goto L1 } if true { goto L2 } L2: } `, } pkgnames := []string{ "fmt", "math", } // parse package files fset := token.NewFileSet() var files []*ast.File for i, src := range sources { f, err := parser.ParseFile(fset, fmt.Sprintf("sources[%d]", i), src, parser.DeclarationErrors) if err != nil { t.Fatal(err) } files = append(files, f) } // resolve and type-check package AST importer := new(resolveTestImporter) importer.files = files conf := Config{ Importer: importer, AllowUseUninitializedVars: true, AllowUninitializedExprs: true, } uses := make(map[*ast.Ident]Object) defs := make(map[*ast.Ident]Object) _, err := conf.Check("testResolveIdents", fset, files, &Info{Defs: defs, Uses: uses}) if err != nil { t.Fatal(err) } // check that all packages were imported for _, name := range pkgnames { if !importer.imported[name] { t.Errorf("package %s not imported", name) } } // check that qualified identifiers are resolved for _, f := range files { ast.Inspect(f, func(n ast.Node) bool { if s, ok := n.(*ast.SelectorExpr); ok { if x, ok := s.X.(*ast.Ident); ok { obj := uses[x] if obj == nil { t.Errorf("%s: unresolved qualified identifier %s", fset.Position(x.Pos()), x.Name) return false } if _, ok := obj.(*PkgName); ok && uses[s.Sel] == nil { t.Errorf("%s: unresolved selector %s", fset.Position(s.Sel.Pos()), s.Sel.Name) return false } return false } return false } return true }) } for id, obj := range uses { if obj == nil { t.Errorf("%s: Uses[%s] == nil", fset.Position(id.Pos()), id.Name) } } // check that each identifier in the source is found in uses or defs or both var both []string for _, f := range files { ast.Inspect(f, func(n ast.Node) bool { if x, ok := n.(*ast.Ident); ok { var objects int if _, found := uses[x]; found { objects |= 1 delete(uses, x) } if _, found := defs[x]; found { objects |= 2 delete(defs, x) } if objects == 0 { t.Errorf("%s: unresolved identifier %s", fset.Position(x.Pos()), x.Name) } else if objects == 3 { both = append(both, x.Name) } return false } return true }) } // check the expected set of idents that are simultaneously uses and defs sort.Strings(both) if got, want := fmt.Sprint(both), "[Mutex Stringer error]"; got != want { t.Errorf("simultaneous uses/defs = %s, want %s", got, want) } // any left-over identifiers didn't exist in the source for x := range uses { t.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name) } for x := range defs { t.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name) } // TODO(gri) add tests to check ImplicitObj callbacks }