blob: 87e3e93da73a0ef79334020405e92386f147e584 [file] [log] [blame]
// Copyright 2011 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 the Check function, which typechecks a package.
package types
import (
"fmt"
"go/ast"
"go/scanner"
"go/token"
"os"
"strconv"
)
const debug = false
type checker struct {
fset *token.FileSet
scanner.ErrorVector
types map[ast.Expr]Type
}
func (c *checker) errorf(pos token.Pos, format string, args ...interface{}) string {
msg := fmt.Sprintf(format, args...)
c.Error(c.fset.Position(pos), msg)
return msg
}
// collectFields collects struct fields tok = token.STRUCT), interface methods
// (tok = token.INTERFACE), and function arguments/results (tok = token.FUNC).
func (c *checker) collectFields(tok token.Token, list *ast.FieldList, cycleOk bool) (fields ObjList, tags []string, isVariadic bool) {
if list != nil {
for _, field := range list.List {
ftype := field.Type
if t, ok := ftype.(*ast.Ellipsis); ok {
ftype = t.Elt
isVariadic = true
}
typ := c.makeType(ftype, cycleOk)
tag := ""
if field.Tag != nil {
assert(field.Tag.Kind == token.STRING)
tag, _ = strconv.Unquote(field.Tag.Value)
}
if len(field.Names) > 0 {
// named fields
for _, name := range field.Names {
obj := name.Obj
obj.Type = typ
fields = append(fields, obj)
if tok == token.STRUCT {
tags = append(tags, tag)
}
}
} else {
// anonymous field
switch tok {
case token.STRUCT:
tags = append(tags, tag)
fallthrough
case token.FUNC:
obj := ast.NewObj(ast.Var, "")
obj.Type = typ
fields = append(fields, obj)
case token.INTERFACE:
utyp := Underlying(typ)
if typ, ok := utyp.(*Interface); ok {
// TODO(gri) This is not good enough. Check for double declarations!
fields = append(fields, typ.Methods...)
} else if _, ok := utyp.(*Bad); !ok {
// if utyp is Bad, don't complain (the root cause was reported before)
c.errorf(ftype.Pos(), "interface contains embedded non-interface type")
}
default:
panic("unreachable")
}
}
}
}
return
}
// makeType makes a new type for an AST type specification x or returns
// the type referred to by a type name x. If cycleOk is set, a type may
// refer to itself directly or indirectly; otherwise cycles are errors.
//
func (c *checker) makeType(x ast.Expr, cycleOk bool) (typ Type) {
if debug {
fmt.Printf("makeType (cycleOk = %v)\n", cycleOk)
ast.Print(c.fset, x)
defer func() {
fmt.Printf("-> %T %v\n\n", typ, typ)
}()
}
switch t := x.(type) {
case *ast.BadExpr:
return &Bad{}
case *ast.Ident:
// type name
obj := t.Obj
if obj == nil {
// unresolved identifier (error has been reported before)
return &Bad{Msg: "unresolved identifier"}
}
if obj.Kind != ast.Typ {
msg := c.errorf(t.Pos(), "%s is not a type", t.Name)
return &Bad{Msg: msg}
}
c.checkObj(obj, cycleOk)
if !cycleOk && obj.Type.(*Name).Underlying == nil {
// TODO(gri) Enable this message again once its position
// is independent of the underlying map implementation.
// msg := c.errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name)
msg := "illegal cycle"
return &Bad{Msg: msg}
}
return obj.Type.(Type)
case *ast.ParenExpr:
return c.makeType(t.X, cycleOk)
case *ast.SelectorExpr:
// qualified identifier
// TODO (gri) eventually, this code belongs to expression
// type checking - here for the time being
if ident, ok := t.X.(*ast.Ident); ok {
if obj := ident.Obj; obj != nil {
if obj.Kind != ast.Pkg {
msg := c.errorf(ident.Pos(), "%s is not a package", obj.Name)
return &Bad{Msg: msg}
}
// TODO(gri) we have a package name but don't
// have the mapping from package name to package
// scope anymore (created in ast.NewPackage).
return &Bad{} // for now
}
}
// TODO(gri) can this really happen (the parser should have excluded this)?
msg := c.errorf(t.Pos(), "expected qualified identifier")
return &Bad{Msg: msg}
case *ast.StarExpr:
return &Pointer{Base: c.makeType(t.X, true)}
case *ast.ArrayType:
if t.Len != nil {
// TODO(gri) compute length
return &Array{Elt: c.makeType(t.Elt, cycleOk)}
}
return &Slice{Elt: c.makeType(t.Elt, true)}
case *ast.StructType:
fields, tags, _ := c.collectFields(token.STRUCT, t.Fields, cycleOk)
return &Struct{Fields: fields, Tags: tags}
case *ast.FuncType:
params, _, _ := c.collectFields(token.FUNC, t.Params, true)
results, _, isVariadic := c.collectFields(token.FUNC, t.Results, true)
return &Func{Recv: nil, Params: params, Results: results, IsVariadic: isVariadic}
case *ast.InterfaceType:
methods, _, _ := c.collectFields(token.INTERFACE, t.Methods, cycleOk)
methods.Sort()
return &Interface{Methods: methods}
case *ast.MapType:
return &Map{Key: c.makeType(t.Key, true), Elt: c.makeType(t.Key, true)}
case *ast.ChanType:
return &Chan{Dir: t.Dir, Elt: c.makeType(t.Value, true)}
}
panic(fmt.Sprintf("unreachable (%T)", x))
}
// checkObj type checks an object.
func (c *checker) checkObj(obj *ast.Object, ref bool) {
if obj.Type != nil {
// object has already been type checked
return
}
switch obj.Kind {
case ast.Bad:
// ignore
case ast.Con:
// TODO(gri) complete this
case ast.Typ:
typ := &Name{Obj: obj}
obj.Type = typ // "mark" object so recursion terminates
typ.Underlying = Underlying(c.makeType(obj.Decl.(*ast.TypeSpec).Type, ref))
case ast.Var:
// TODO(gri) complete this
case ast.Fun:
// TODO(gri) complete this
default:
panic("unreachable")
}
}
// Check typechecks a package.
// It augments the AST by assigning types to all ast.Objects and returns a map
// of types for all expression nodes in statements, and a scanner.ErrorList if
// there are errors.
//
func Check(fset *token.FileSet, pkg *ast.Package) (types map[ast.Expr]Type, err os.Error) {
var c checker
c.fset = fset
c.types = make(map[ast.Expr]Type)
for _, obj := range pkg.Scope.Objects {
c.checkObj(obj, false)
}
return c.types, c.GetError(scanner.NoMultiples)
}