+++ /dev/null
-// Copyright 2010 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// DEPRECATED PACKAGE - SEE go/types INSTEAD.
-// This package implements typechecking of a Go AST.
-// The result of the typecheck is an augmented AST
-// with object and type information for each identifier.
-//
-package typechecker
-
-import (
- "fmt"
- "go/ast"
- "go/token"
- "go/scanner"
- "os"
-)
-
-// TODO(gri) don't report errors for objects/types that are marked as bad.
-
-
-const debug = true // set for debugging output
-
-// An importer takes an import path and returns the data describing the
-// respective package's exported interface. The data format is TBD.
-//
-type Importer func(path string) ([]byte, os.Error)
-
-// CheckPackage typechecks a package and augments the AST by setting
-// *ast.Object, *ast.Type, and *ast.Scope fields accordingly. If an
-// importer is provided, it is used to handle imports, otherwise they
-// are ignored (likely leading to typechecking errors).
-//
-// If errors are reported, the AST may be incompletely augmented (fields
-// may be nil) or contain incomplete object, type, or scope information.
-//
-func CheckPackage(fset *token.FileSet, pkg *ast.Package, importer Importer) os.Error {
- var tc typechecker
- tc.fset = fset
- tc.importer = importer
- tc.checkPackage(pkg)
- return tc.GetError(scanner.Sorted)
-}
-
-// CheckFile typechecks a single file, but otherwise behaves like
-// CheckPackage. If the complete package consists of more than just
-// one file, the file may not typecheck without errors.
-//
-func CheckFile(fset *token.FileSet, file *ast.File, importer Importer) os.Error {
- // create a single-file dummy package
- pkg := &ast.Package{file.Name.Name, nil, nil, map[string]*ast.File{fset.Position(file.Name.NamePos).Filename: file}}
- return CheckPackage(fset, pkg, importer)
-}
-
-// ----------------------------------------------------------------------------
-// Typechecker state
-
-type typechecker struct {
- fset *token.FileSet
- scanner.ErrorVector
- importer Importer
- globals []*ast.Object // list of global objects
- topScope *ast.Scope // current top-most scope
- cyclemap map[*ast.Object]bool // for cycle detection
- iota int // current value of iota
-}
-
-func (tc *typechecker) Errorf(pos token.Pos, format string, args ...interface{}) {
- tc.Error(tc.fset.Position(pos), fmt.Sprintf(format, args...))
-}
-
-func assert(pred bool) {
- if !pred {
- panic("internal error")
- }
-}
-
-/*
-Typechecking is done in several phases:
-
-phase 1: declare all global objects; also collect all function and method declarations
- - all objects have kind, name, decl fields; the decl field permits
- quick lookup of an object's declaration
- - constant objects have an iota value
- - type objects have unresolved types with empty scopes, all others have nil types
- - report global double declarations
-
-phase 2: bind methods to their receiver base types
- - receiver base types must be declared in the package, thus for
- each method a corresponding (unresolved) type must exist
- - report method double declarations and errors with base types
-
-phase 3: resolve all global objects
- - sequentially iterate through all objects in the global scope
- - resolve types for all unresolved types and assign types to
- all attached methods
- - assign types to all other objects, possibly by evaluating
- constant and initializer expressions
- - resolution may recurse; a cyclemap is used to detect cycles
- - report global typing errors
-
-phase 4: sequentially typecheck function and method bodies
- - all global objects are declared and have types and values;
- all methods have types
- - sequentially process statements in each body; any object
- referred to must be fully defined at this point
- - report local typing errors
-*/
-
-func (tc *typechecker) checkPackage(pkg *ast.Package) {
- // setup package scope
- tc.topScope = Universe
- tc.openScope()
- defer tc.closeScope()
-
- // TODO(gri) there's no file scope at the moment since we ignore imports
-
- // phase 1: declare all global objects; also collect all function and method declarations
- var funcs []*ast.FuncDecl
- for _, file := range pkg.Files {
- for _, decl := range file.Decls {
- tc.declGlobal(decl)
- if f, isFunc := decl.(*ast.FuncDecl); isFunc {
- funcs = append(funcs, f)
- }
- }
- }
-
- // phase 2: bind methods to their receiver base types
- for _, m := range funcs {
- if m.Recv != nil {
- tc.bindMethod(m)
- }
- }
-
- // phase 3: resolve all global objects
- tc.cyclemap = make(map[*ast.Object]bool)
- for _, obj := range tc.globals {
- tc.resolve(obj)
- }
- assert(len(tc.cyclemap) == 0)
-
- // 4: sequentially typecheck function and method bodies
- for _, f := range funcs {
- ftype, _ := f.Name.Obj.Type.(*Type)
- tc.checkBlock(f.Body.List, ftype)
- }
-
- pkg.Scope = tc.topScope
-}
-
-func (tc *typechecker) declGlobal(global ast.Decl) {
- switch d := global.(type) {
- case *ast.BadDecl:
- // ignore
-
- case *ast.GenDecl:
- iota := 0
- var prev *ast.ValueSpec
- for _, spec := range d.Specs {
- switch s := spec.(type) {
- case *ast.ImportSpec:
- // TODO(gri) imports go into file scope
- case *ast.ValueSpec:
- switch d.Tok {
- case token.CONST:
- if s.Values == nil {
- // create a new spec with type and values from the previous one
- if prev != nil {
- s = &ast.ValueSpec{s.Doc, s.Names, prev.Type, prev.Values, s.Comment}
- } else {
- // TODO(gri) this should probably go into the const decl code
- tc.Errorf(s.Pos(), "missing initializer for const %s", s.Names[0].Name)
- }
- }
- for _, name := range s.Names {
- tc.globals = append(tc.globals, tc.decl(ast.Con, name, s, iota))
- }
- case token.VAR:
- for _, name := range s.Names {
- tc.globals = append(tc.globals, tc.decl(ast.Var, name, s, 0))
- }
- default:
- panic("unreachable")
- }
- prev = s
- iota++
- case *ast.TypeSpec:
- obj := tc.decl(ast.Typ, s.Name, s, 0)
- tc.globals = append(tc.globals, obj)
- // give all type objects an unresolved type so
- // that we can collect methods in the type scope
- typ := NewType(Unresolved)
- obj.Type = typ
- typ.Obj = obj
- default:
- panic("unreachable")
- }
- }
-
- case *ast.FuncDecl:
- if d.Recv == nil {
- tc.globals = append(tc.globals, tc.decl(ast.Fun, d.Name, d, 0))
- }
-
- default:
- panic("unreachable")
- }
-}
-
-// If x is of the form *T, deref returns T, otherwise it returns x.
-func deref(x ast.Expr) ast.Expr {
- if p, isPtr := x.(*ast.StarExpr); isPtr {
- x = p.X
- }
- return x
-}
-
-func (tc *typechecker) bindMethod(method *ast.FuncDecl) {
- // a method is declared in the receiver base type's scope
- var scope *ast.Scope
- base := deref(method.Recv.List[0].Type)
- if name, isIdent := base.(*ast.Ident); isIdent {
- // if base is not an *ast.Ident, we had a syntax
- // error and the parser reported an error already
- obj := tc.topScope.Lookup(name.Name)
- if obj == nil {
- tc.Errorf(name.Pos(), "invalid receiver: %s is not declared in this package", name.Name)
- } else if obj.Kind != ast.Typ {
- tc.Errorf(name.Pos(), "invalid receiver: %s is not a type", name.Name)
- } else {
- typ := obj.Type.(*Type)
- assert(typ.Form == Unresolved)
- scope = typ.Scope
- }
- }
- if scope == nil {
- // no receiver type found; use a dummy scope
- // (we still want to type-check the method
- // body, so make sure there is a name object
- // and type)
- // TODO(gri) should we record the scope so
- // that we don't lose the receiver for type-
- // checking of the method body?
- scope = ast.NewScope(nil)
- }
- tc.declInScope(scope, ast.Fun, method.Name, method, 0)
-}
-
-func (tc *typechecker) resolve(obj *ast.Object) {
- // check for declaration cycles
- if tc.cyclemap[obj] {
- tc.Errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name)
- obj.Kind = ast.Bad
- return
- }
- tc.cyclemap[obj] = true
- defer func() {
- tc.cyclemap[obj] = false, false
- }()
-
- // resolve non-type objects
- typ, _ := obj.Type.(*Type)
- if typ == nil {
- switch obj.Kind {
- case ast.Bad:
- // ignore
-
- case ast.Con:
- tc.declConst(obj)
-
- case ast.Var:
- tc.declVar(obj)
- obj.Type = tc.typeFor(nil, obj.Decl.(*ast.ValueSpec).Type, false)
-
- case ast.Fun:
- obj.Type = NewType(Function)
- t := obj.Decl.(*ast.FuncDecl).Type
- tc.declSignature(obj.Type.(*Type), nil, t.Params, t.Results)
-
- default:
- // type objects have non-nil types when resolve is called
- if debug {
- fmt.Printf("kind = %s\n", obj.Kind)
- }
- panic("unreachable")
- }
- return
- }
-
- // resolve type objects
- if typ.Form == Unresolved {
- tc.typeFor(typ, typ.Obj.Decl.(*ast.TypeSpec).Type, false)
-
- // provide types for all methods
- for _, obj := range typ.Scope.Objects {
- if obj.Kind == ast.Fun {
- assert(obj.Type == nil)
- obj.Type = NewType(Method)
- f := obj.Decl.(*ast.FuncDecl)
- t := f.Type
- tc.declSignature(obj.Type.(*Type), f.Recv, t.Params, t.Results)
- }
- }
- }
-}
-
-func (tc *typechecker) checkBlock(body []ast.Stmt, ftype *Type) {
- tc.openScope()
- defer tc.closeScope()
-
- // inject function/method parameters into block scope, if any
- if ftype != nil {
- for _, par := range ftype.Params.Objects {
- if par.Name != "_" {
- alt := tc.topScope.Insert(par)
- assert(alt == nil) // ftype has no double declarations
- }
- }
- }
-
- for _, stmt := range body {
- tc.checkStmt(stmt)
- }
-}
-
-// ----------------------------------------------------------------------------
-// Types
-
-// unparen removes parentheses around x, if any.
-func unparen(x ast.Expr) ast.Expr {
- if ux, hasParens := x.(*ast.ParenExpr); hasParens {
- return unparen(ux.X)
- }
- return x
-}
-
-func (tc *typechecker) declFields(scope *ast.Scope, fields *ast.FieldList, ref bool) (n uint) {
- if fields != nil {
- for _, f := range fields.List {
- typ := tc.typeFor(nil, f.Type, ref)
- for _, name := range f.Names {
- fld := tc.declInScope(scope, ast.Var, name, f, 0)
- fld.Type = typ
- n++
- }
- }
- }
- return n
-}
-
-func (tc *typechecker) declSignature(typ *Type, recv, params, results *ast.FieldList) {
- assert((typ.Form == Method) == (recv != nil))
- typ.Params = ast.NewScope(nil)
- tc.declFields(typ.Params, recv, true)
- tc.declFields(typ.Params, params, true)
- typ.N = tc.declFields(typ.Params, results, true)
-}
-
-func (tc *typechecker) typeFor(def *Type, x ast.Expr, ref bool) (typ *Type) {
- x = unparen(x)
-
- // type name
- if t, isIdent := x.(*ast.Ident); isIdent {
- obj := tc.find(t)
-
- if obj.Kind != ast.Typ {
- tc.Errorf(t.Pos(), "%s is not a type", t.Name)
- if def == nil {
- typ = NewType(BadType)
- } else {
- typ = def
- typ.Form = BadType
- }
- typ.Expr = x
- return
- }
-
- if !ref {
- tc.resolve(obj) // check for cycles even if type resolved
- }
- typ = obj.Type.(*Type)
-
- if def != nil {
- // new type declaration: copy type structure
- def.Form = typ.Form
- def.N = typ.N
- def.Key, def.Elt = typ.Key, typ.Elt
- def.Params = typ.Params
- def.Expr = x
- typ = def
- }
- return
- }
-
- // type literal
- typ = def
- if typ == nil {
- typ = NewType(BadType)
- }
- typ.Expr = x
-
- switch t := x.(type) {
- case *ast.SelectorExpr:
- if debug {
- fmt.Println("qualified identifier unimplemented")
- }
- typ.Form = BadType
-
- case *ast.StarExpr:
- typ.Form = Pointer
- typ.Elt = tc.typeFor(nil, t.X, true)
-
- case *ast.ArrayType:
- if t.Len != nil {
- typ.Form = Array
- // TODO(gri) compute the real length
- // (this may call resolve recursively)
- (*typ).N = 42
- } else {
- typ.Form = Slice
- }
- typ.Elt = tc.typeFor(nil, t.Elt, t.Len == nil)
-
- case *ast.StructType:
- typ.Form = Struct
- tc.declFields(typ.Scope, t.Fields, false)
-
- case *ast.FuncType:
- typ.Form = Function
- tc.declSignature(typ, nil, t.Params, t.Results)
-
- case *ast.InterfaceType:
- typ.Form = Interface
- tc.declFields(typ.Scope, t.Methods, true)
-
- case *ast.MapType:
- typ.Form = Map
- typ.Key = tc.typeFor(nil, t.Key, true)
- typ.Elt = tc.typeFor(nil, t.Value, true)
-
- case *ast.ChanType:
- typ.Form = Channel
- typ.N = uint(t.Dir)
- typ.Elt = tc.typeFor(nil, t.Value, true)
-
- default:
- if debug {
- fmt.Printf("x is %T\n", x)
- }
- panic("unreachable")
- }
-
- return
-}
-
-// ----------------------------------------------------------------------------
-// TODO(gri) implement these place holders
-
-func (tc *typechecker) declConst(*ast.Object) {
-}
-
-func (tc *typechecker) declVar(*ast.Object) {
-}
-
-func (tc *typechecker) checkStmt(ast.Stmt) {
-}
+++ /dev/null
-// Copyright 2010 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// This file implements a simple typechecker test harness. Packages found
-// in the testDir directory are typechecked. Error messages reported by
-// the typechecker are compared against the error messages expected for
-// the test files.
-//
-// Expected errors are indicated in the test files by putting a comment
-// of the form /* ERROR "rx" */ immediately following an offending token.
-// The harness will verify that an error matching the regular expression
-// rx is reported at that source position. Consecutive comments may be
-// used to indicate multiple errors for the same token position.
-//
-// For instance, the following test file indicates that a "not declared"
-// error should be reported for the undeclared variable x:
-//
-// package P0
-// func f() {
-// _ = x /* ERROR "not declared" */ + 1
-// }
-//
-// If the -pkg flag is set, only packages with package names matching
-// the regular expression provided via the flag value are tested.
-
-package typechecker
-
-import (
- "flag"
- "fmt"
- "go/ast"
- "go/parser"
- "go/scanner"
- "go/token"
- "io/ioutil"
- "os"
- "regexp"
- "sort"
- "strings"
- "testing"
-)
-
-const testDir = "./testdata" // location of test packages
-
-var fset = token.NewFileSet()
-
-var (
- pkgPat = flag.String("pkg", ".*", "regular expression to select test packages by package name")
- trace = flag.Bool("trace", false, "print package names")
-)
-
-// ERROR comments must be of the form /* ERROR "rx" */ and rx is
-// a regular expression that matches the expected error message.
-var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`)
-
-// expectedErrors collects the regular expressions of ERROR comments
-// found in the package files of pkg and returns them in sorted order
-// (by filename and position).
-func expectedErrors(t *testing.T, pkg *ast.Package) (list scanner.ErrorList) {
- // scan all package files
- for filename := range pkg.Files {
- src, err := ioutil.ReadFile(filename)
- if err != nil {
- t.Fatalf("expectedErrors(%s): %v", pkg.Name, err)
- }
-
- var s scanner.Scanner
- file := fset.AddFile(filename, fset.Base(), len(src))
- s.Init(file, src, nil, scanner.ScanComments)
- var prev token.Pos // position of last non-comment token
- loop:
- for {
- pos, tok, lit := s.Scan()
- switch tok {
- case token.EOF:
- break loop
- case token.COMMENT:
- s := errRx.FindStringSubmatch(lit)
- if len(s) == 2 {
- list = append(list, &scanner.Error{fset.Position(prev), string(s[1])})
- }
- default:
- prev = pos
- }
- }
- }
- sort.Sort(list) // multiple files may not be sorted
- return
-}
-
-func testFilter(f *os.FileInfo) bool {
- return strings.HasSuffix(f.Name, ".src") && f.Name[0] != '.'
-}
-
-func checkError(t *testing.T, expected, found *scanner.Error) {
- rx, err := regexp.Compile(expected.Msg)
- if err != nil {
- t.Errorf("%s: %v", expected.Pos, err)
- return
- }
-
- match := rx.MatchString(found.Msg)
-
- if expected.Pos.Offset != found.Pos.Offset {
- if match {
- t.Errorf("%s: expected error should have been at %s", expected.Pos, found.Pos)
- } else {
- t.Errorf("%s: error matching %q expected", expected.Pos, expected.Msg)
- return
- }
- }
-
- if !match {
- t.Errorf("%s: %q does not match %q", expected.Pos, expected.Msg, found.Msg)
- }
-}
-
-func TestTypeCheck(t *testing.T) {
- flag.Parse()
- pkgRx, err := regexp.Compile(*pkgPat)
- if err != nil {
- t.Fatalf("illegal flag value %q: %s", *pkgPat, err)
- }
-
- pkgs, err := parser.ParseDir(fset, testDir, testFilter, 0)
- if err != nil {
- scanner.PrintError(os.Stderr, err)
- t.Fatalf("packages in %s contain syntax errors", testDir)
- }
-
- for _, pkg := range pkgs {
- if !pkgRx.MatchString(pkg.Name) {
- continue // only test selected packages
- }
-
- if *trace {
- fmt.Println(pkg.Name)
- }
-
- xlist := expectedErrors(t, pkg)
- err := CheckPackage(fset, pkg, nil)
- if err != nil {
- if elist, ok := err.(scanner.ErrorList); ok {
- // verify that errors match
- for i := 0; i < len(xlist) && i < len(elist); i++ {
- checkError(t, xlist[i], elist[i])
- }
- // the correct number or errors must have been found
- if len(xlist) != len(elist) {
- fmt.Fprintf(os.Stderr, "%s\n", pkg.Name)
- scanner.PrintError(os.Stderr, elist)
- fmt.Fprintln(os.Stderr)
- t.Errorf("TypeCheck(%s): %d errors expected but %d reported", pkg.Name, len(xlist), len(elist))
- }
- } else {
- t.Errorf("TypeCheck(%s): %v", pkg.Name, err)
- }
- } else if len(xlist) > 0 {
- t.Errorf("TypeCheck(%s): %d errors expected but 0 reported", pkg.Name, len(xlist))
- }
- }
-}