blob: b151f5834da1d9e6ce612b56f6df52b2c9da3faf [file] [log] [blame]
// 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) {
}