go.tools/go/types: resolve objects in type checker

By setting resolve = true in check.go, the type checker
will do all identifier resolution during type checking
time and ignore (and not depend on) parser objects. This
permits the type checker to run easily on ASTs that are
not generated with invariants guaranteed by the parser.

There is a lot of new code; much of it slightly modified
copies of old code. There is also a lot of duplication.
After removing the dead code resulting from resolve = true
permanently (and removing the flag as well), it will be
easier to perform a thorough cleanup. As is, there are
too many intertwined code paths.

For now resolve = false. To be enabled in a successor CL.

R=adonovan
CC=golang-dev
https://golang.org/cl/9606045
This commit is contained in:
Robert Griesemer 2013-05-28 10:06:37 -07:00
parent 98bcbfbab7
commit 3df6f127f0
21 changed files with 1283 additions and 147 deletions

156
go/types/assignments.go Normal file
View File

@ -0,0 +1,156 @@
// Copyright 2013 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.
package types
import (
"go/ast"
"code.google.com/p/go.tools/go/exact"
)
// TODO(gri) initialize is very close to the 2nd half of assign1to1.
func (check *checker) assign(obj Object, x *operand) {
// Determine typ of lhs: If the object doesn't have a type
// yet, determine it from the type of x; if x is invalid,
// set the object type to Typ[Invalid].
var typ Type
switch obj := obj.(type) {
default:
unreachable()
case *Const:
typ = obj.typ // may already be Typ[Invalid]
if typ == nil {
typ = Typ[Invalid]
if x.mode != invalid {
typ = x.typ
}
obj.typ = typ
}
case *Var:
typ = obj.typ // may already be Typ[Invalid]
if typ == nil {
typ = Typ[Invalid]
if x.mode != invalid {
typ = x.typ
if isUntyped(typ) {
// convert untyped types to default types
if typ == Typ[UntypedNil] {
check.errorf(x.pos(), "use of untyped nil")
typ = Typ[Invalid]
} else {
typ = defaultType(typ)
}
}
}
obj.typ = typ
}
}
// nothing else to check if we don't have a valid lhs or rhs
if typ == Typ[Invalid] || x.mode == invalid {
return
}
if !check.assignment(x, typ) {
if x.mode != invalid {
if x.typ != Typ[Invalid] && typ != Typ[Invalid] {
check.errorf(x.pos(), "cannot initialize %s (type %s) with %s", obj.Name(), typ, x)
}
}
return
}
// for constants, set their value
if obj, _ := obj.(*Const); obj != nil {
obj.val = exact.MakeUnknown() // failure case: we don't know the constant value
if x.mode == constant {
if isConstType(x.typ) {
obj.val = x.val
} else if x.typ != Typ[Invalid] {
check.errorf(x.pos(), "%s has invalid constant type", x)
}
} else if x.mode != invalid {
check.errorf(x.pos(), "%s is not constant", x)
}
}
}
func (check *checker) assignMulti(lhs []Object, rhs []ast.Expr) {
assert(len(lhs) > 0)
const decl = false
// If the lhs and rhs have corresponding expressions, treat each
// matching pair as an individual pair.
if len(lhs) == len(rhs) {
var x operand
for i, e := range rhs {
check.expr(&x, e, nil, -1)
if x.mode == invalid {
goto Error
}
check.assign(lhs[i], &x)
}
return
}
// Otherwise, the rhs must be a single expression (possibly
// a function call returning multiple values, or a comma-ok
// expression).
if len(rhs) == 1 {
// len(lhs) > 1
// Start with rhs so we have expression types
// for declarations with implicit types.
var x operand
check.expr(&x, rhs[0], nil, -1)
if x.mode == invalid {
goto Error
}
if t, ok := x.typ.(*Tuple); ok && len(lhs) == t.Len() {
// function result
x.mode = value
for i := 0; i < len(lhs); i++ {
obj := t.At(i)
x.expr = nil // TODO(gri) should do better here
x.typ = obj.typ
check.assign(lhs[i], &x)
}
return
}
if x.mode == valueok && len(lhs) == 2 {
// comma-ok expression
x.mode = value
check.assign(lhs[0], &x)
x.typ = Typ[UntypedBool]
check.assign(lhs[1], &x)
return
}
}
check.errorf(lhs[0].Pos(), "assignment count mismatch: %d = %d", len(lhs), len(rhs))
Error:
// In case of a declaration, set all lhs types to Typ[Invalid].
for _, obj := range lhs {
switch obj := obj.(type) {
case *Const:
if obj.typ == nil {
obj.typ = Typ[Invalid]
}
obj.val = exact.MakeUnknown()
case *Var:
if obj.typ == nil {
obj.typ = Typ[Invalid]
}
default:
unreachable()
}
}
}

View File

@ -14,10 +14,12 @@ import (
"code.google.com/p/go.tools/go/exact"
)
// debugging support
// debugging/development support
const (
debug = true // leave on during development
trace = false // turn on for detailed type resolution traces
// TODO(gri) remove this flag and clean up code under the assumption that resolve == true.
resolve = false // if set, resolve all identifiers in the type checker (don't use ast.Objects anymore)
)
// exprInfo stores type and constant value for an untyped expression.
@ -29,9 +31,8 @@ type exprInfo struct {
// A checker is an instance of the type checker.
type checker struct {
ctxt *Context
fset *token.FileSet
files []*ast.File
ctxt *Context
fset *token.FileSet
// lazily initialized
pkg *Package // current package
@ -47,9 +48,29 @@ type checker struct {
funclist []function // list of functions/methods with correct signatures and non-empty bodies
funcsig *Signature // signature of currently typechecked function
pos []token.Pos // stack of expr positions; debugging support, used if trace is set
// these are only valid if resolve is set
objMap map[Object]*decl // if set we are in the package-global declaration phase (otherwise all objects seen must be declared)
topScope *Scope // topScope for lookups, non-global declarations
}
func (check *checker) openScope() {
check.topScope = &Scope{Outer: check.topScope}
}
func (check *checker) closeScope() {
check.topScope = check.topScope.Outer
}
func (check *checker) register(id *ast.Ident, obj Object) {
if resolve {
// TODO(gri) Document how if an identifier can be registered more than once.
if f := check.ctxt.Ident; f != nil {
f(id, obj)
}
return
}
// When an expression is evaluated more than once (happens
// in rare cases, e.g. for statement expressions, see
// comment in stmt.go), the object has been registered
@ -70,9 +91,21 @@ func (check *checker) register(id *ast.Ident, obj Object) {
// uses the checker.objects map.
//
// TODO(gri) Once identifier resolution is done entirely by
// the typechecker, only the idents map is needed.
// the typechecker, only scopes are needed. Need
// to update the comment above.
//
func (check *checker) lookup(ident *ast.Ident) Object {
if resolve {
for scope := check.topScope; scope != nil; scope = scope.Outer {
if obj := scope.Lookup(ident.Name); obj != nil {
check.register(ident, obj)
return obj
}
}
return nil
}
// old code
obj := check.idents[ident]
astObj := ident.Obj
@ -95,7 +128,8 @@ func (check *checker) lookup(ident *ast.Ident) Object {
}
type function struct {
obj *Func // for debugging/tracing only
file *Scope // only valid if resolve is set
obj *Func // for debugging/tracing only
sig *Signature
body *ast.BlockStmt
}
@ -107,11 +141,14 @@ type function struct {
func (check *checker) later(f *Func, sig *Signature, body *ast.BlockStmt) {
// functions implemented elsewhere (say in assembly) have no body
if body != nil {
check.funclist = append(check.funclist, function{f, sig, body})
check.funclist = append(check.funclist, function{check.topScope, f, sig, body})
}
}
func (check *checker) declareIdent(scope *Scope, ident *ast.Ident, obj Object) {
if resolve {
unreachable()
}
assert(check.lookup(ident) == nil) // identifier already declared or resolved
check.register(ident, obj)
if ident.Name != "_" {
@ -126,6 +163,10 @@ func (check *checker) declareIdent(scope *Scope, ident *ast.Ident, obj Object) {
}
func (check *checker) valueSpec(pos token.Pos, obj Object, lhs []*ast.Ident, spec *ast.ValueSpec, iota int) {
if resolve {
unreachable()
}
if len(lhs) == 0 {
check.invalidAST(pos, "missing lhs in declaration")
return
@ -150,16 +191,17 @@ func (check *checker) valueSpec(pos token.Pos, obj Object, lhs []*ast.Ident, spe
break
}
}
assert(l != nil)
isConst := false
switch obj := obj.(type) {
case *Const:
obj.typ = typ
isConst = true
case *Var:
obj.typ = typ
default:
unreachable()
}
check.assign1to1(l, r, nil, true, iota)
check.assign1to1(l, r, nil, true, iota, isConst)
return
}
@ -190,21 +232,30 @@ func (check *checker) valueSpec(pos token.Pos, obj Object, lhs []*ast.Ident, spe
for i, e := range lhs {
lhx[i] = e
}
check.assignNtoM(lhx, rhs, true, iota)
_, isConst := obj.(*Const)
check.assignNtoM(lhx, rhs, true, iota, isConst)
}
}
// object typechecks an object by assigning it a type.
//
func (check *checker) object(obj Object, cycleOk bool) {
if resolve {
unreachable()
}
if obj.Type() != nil {
return // already checked
}
switch obj := obj.(type) {
case *Package:
// nothing to do
if resolve {
unreachable()
}
case *Const:
if obj.typ != nil {
return // already checked
}
// The obj.Val field for constants is initialized to its respective
// iota value (type int) by the parser.
// If the object's type is Typ[Invalid], the object value is ignored.
@ -214,7 +265,7 @@ func (check *checker) object(obj Object, cycleOk bool) {
// know that x is a constant and has type float32, but we don't
// have a value due to the error in the conversion).
if obj.visited {
check.errorf(obj.Pos(), "illegal cycle in initialization of constant %s", obj.Name)
check.errorf(obj.Pos(), "illegal cycle in initialization of constant %s", obj.name)
obj.typ = Typ[Invalid]
return
}
@ -231,11 +282,8 @@ func (check *checker) object(obj Object, cycleOk bool) {
check.valueSpec(spec.Pos(), obj, spec.Names, init, int(iota))
case *Var:
if obj.typ != nil {
return // already checked
}
if obj.visited {
check.errorf(obj.Pos(), "illegal cycle in initialization of variable %s", obj.Name)
check.errorf(obj.Pos(), "illegal cycle in initialization of variable %s", obj.name)
obj.typ = Typ[Invalid]
return
}
@ -252,9 +300,6 @@ func (check *checker) object(obj Object, cycleOk bool) {
}
case *TypeName:
if obj.typ != nil {
return // already checked
}
typ := &Named{obj: obj}
obj.typ = typ // "mark" object so recursion terminates
typ.underlying = check.typ(obj.spec.Type, cycleOk).Underlying()
@ -265,7 +310,7 @@ func (check *checker) object(obj Object, cycleOk bool) {
// struct fields must not conflict with methods
for _, f := range t.fields {
if m := scope.Lookup(f.Name); m != nil {
check.errorf(m.Pos(), "type %s has both field and method named %s", obj.Name, f.Name)
check.errorf(m.Pos(), "type %s has both field and method named %s", obj.name, f.Name)
// ok to continue
}
}
@ -273,7 +318,7 @@ func (check *checker) object(obj Object, cycleOk bool) {
// methods cannot be associated with an interface type
for _, m := range scope.Entries {
recv := m.(*Func).decl.Recv.List[0].Type
check.errorf(recv.Pos(), "invalid receiver type %s (%s is an interface type)", obj.Name, obj.Name)
check.errorf(recv.Pos(), "invalid receiver type %s (%s is an interface type)", obj.name, obj.name)
// ok to continue
}
}
@ -282,7 +327,7 @@ func (check *checker) object(obj Object, cycleOk bool) {
for _, obj := range scope.Entries {
m := obj.(*Func)
sig := check.typ(m.decl.Type, cycleOk).(*Signature)
params, _ := check.collectParams(m.decl.Recv, false)
params, _ := check.collectParams(sig.scope, m.decl.Recv, false)
sig.recv = params[0] // the parser/assocMethod ensure there is exactly one parameter
m.typ = sig
assert(methods.Insert(obj) == nil)
@ -293,9 +338,6 @@ func (check *checker) object(obj Object, cycleOk bool) {
}
case *Func:
if obj.typ != nil {
return // already checked
}
fdecl := obj.decl
// methods are typechecked when their receivers are typechecked
if fdecl.Recv == nil {
@ -318,6 +360,10 @@ func (check *checker) object(obj Object, cycleOk bool) {
// for constant declarations without explicit initialization expressions.
//
func (check *checker) assocInitvals(decl *ast.GenDecl) {
if resolve {
unreachable()
}
var last *ast.ValueSpec
for _, s := range decl.Specs {
if s, ok := s.(*ast.ValueSpec); ok {
@ -337,6 +383,10 @@ func (check *checker) assocInitvals(decl *ast.GenDecl) {
// receiver base type. meth.Recv must exist.
//
func (check *checker) assocMethod(meth *ast.FuncDecl) {
if resolve {
unreachable()
}
// The receiver type is one of the following (enforced by parser):
// - *ast.Ident
// - *ast.StarExpr{*ast.Ident}
@ -378,6 +428,10 @@ func (check *checker) assocMethod(meth *ast.FuncDecl) {
}
func (check *checker) decl(decl ast.Decl) {
if resolve {
unreachable() // during top-level type-checking
}
switch d := decl.(type) {
case *ast.BadDecl:
// ignore
@ -419,12 +473,17 @@ func (check *checker) decl(decl ast.Decl) {
type bailout struct{}
func check(ctxt *Context, path string, fset *token.FileSet, files ...*ast.File) (pkg *Package, err error) {
pkg = &Package{
path: path,
scope: &Scope{Outer: Universe},
imports: make(map[string]*Package),
}
// initialize checker
check := checker{
ctxt: ctxt,
fset: fset,
files: files,
pkg: &Package{path: path, scope: &Scope{Outer: Universe}, imports: make(map[string]*Package)},
pkg: pkg,
idents: make(map[*ast.Ident]Object),
objects: make(map[*ast.Object]Object),
initspecs: make(map[*ast.ValueSpec]*ast.ValueSpec),
@ -433,9 +492,8 @@ func check(ctxt *Context, path string, fset *token.FileSet, files ...*ast.File)
untyped: make(map[ast.Expr]exprInfo),
}
// set results and handle panics
// handle panics
defer func() {
pkg = check.pkg
switch p := recover().(type) {
case nil, bailout:
// normal return or early exit
@ -451,22 +509,45 @@ func check(ctxt *Context, path string, fset *token.FileSet, files ...*ast.File)
}
}()
// determine package name and files
i := 0
for _, file := range files {
switch name := file.Name.Name; pkg.name {
case "":
pkg.name = name
fallthrough
case name:
files[i] = file
i++
default:
check.errorf(file.Package, "package %s; expected %s", name, pkg.name)
// ignore this file
}
}
files = files[:i]
// resolve identifiers
imp := ctxt.Import
if imp == nil {
imp = GcImport
}
methods := check.resolve(imp)
// associate methods with types
for _, m := range methods {
check.assocMethod(m)
}
if resolve {
check.resolveFiles(files, imp)
// typecheck all declarations
for _, f := range check.files {
for _, d := range f.Decls {
check.decl(d)
} else {
methods := check.resolve(imp, files)
// associate methods with types
for _, m := range methods {
check.assocMethod(m)
}
// typecheck all declarations
for _, f := range files {
for _, d := range f.Decls {
check.decl(d)
}
}
}
@ -481,6 +562,7 @@ func check(ctxt *Context, path string, fset *token.FileSet, files ...*ast.File)
}
fmt.Println("---", s)
}
check.topScope = f.sig.scope // open the function scope
check.funcsig = f.sig
check.stmtList(f.body.List)
if f.sig.results.Len() > 0 && f.body != nil && !check.isTerminating(f.body, "") {

View File

@ -83,7 +83,11 @@ func parseFiles(t *testing.T, testname string, filenames []string) ([]*ast.File,
var files []*ast.File
var errlist []error
for _, filename := range filenames {
file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors|parser.AllErrors)
mode := parser.AllErrors
if !resolve {
mode |= parser.DeclarationErrors
}
file, err := parser.ParseFile(fset, filename, nil, mode)
if file == nil {
t.Fatalf("%s: could not parse file %s", testname, filename)
}
@ -237,6 +241,10 @@ func TestCheck(t *testing.T) {
return
}
if resolve {
fmt.Println("*** Running new code: Identifiers are resolved by type checker. ***")
}
// Otherwise, run all the tests.
for _, test := range tests {
checkFiles(t, test.name, test.files)

View File

@ -53,6 +53,8 @@ func (check *checker) untrace(format string, args ...interface{}) {
func (check *checker) formatMsg(format string, args []interface{}) string {
for i, arg := range args {
switch a := arg.(type) {
case nil:
args[i] = "<nil>"
case operand:
panic("internal error: should always pass *operand")
case *operand:
@ -63,6 +65,8 @@ func (check *checker) formatMsg(format string, args []interface{}) string {
args[i] = exprString(a)
case Type:
args[i] = typeString(a)
case Object:
args[i] = fmt.Sprintf("%s (%T)", a.Name(), a)
}
}
return fmt.Sprintf(format, args...)

View File

@ -7,6 +7,7 @@
package types
import (
"fmt"
"go/ast"
"go/token"
"strconv"
@ -70,7 +71,7 @@ on the way down in updateExprType, or at the end of the type checker run,
if present the Context.Expr method is invoked to notify a go/types client.
*/
func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*Var, isVariadic bool) {
func (check *checker) collectParams(scope *Scope, list *ast.FieldList, variadicOk bool) (params []*Var, isVariadic bool) {
if list == nil {
return
}
@ -92,15 +93,22 @@ func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (param
if len(field.Names) > 0 {
// named parameter
for _, name := range field.Names {
par := check.lookup(name).(*Var)
par.typ = typ
var par *Var
if resolve {
par = &Var{pos: name.Pos(), pkg: check.pkg, name: name.Name, typ: typ, decl: field}
check.declare(scope, par)
check.register(name, par)
} else {
par = check.lookup(name).(*Var)
par.typ = typ
}
last = par
copy := *par
params = append(params, &copy)
}
} else {
// anonymous parameter
par := &Var{typ: typ}
par := &Var{pkg: check.pkg, typ: typ, decl: field}
last = nil // not accessible inside function
params = append(params, par)
}
@ -114,7 +122,7 @@ func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (param
return
}
func (check *checker) collectMethods(list *ast.FieldList) (methods ObjSet) {
func (check *checker) collectMethods(scope *Scope, list *ast.FieldList) (methods ObjSet) {
if list == nil {
return
}
@ -131,10 +139,20 @@ func (check *checker) collectMethods(list *ast.FieldList) (methods ObjSet) {
continue
}
for _, name := range f.Names {
// TODO(gri) provide correct declaration info
obj := &Func{check.pkg, name.Name, sig, nil}
if alt := methods.Insert(obj); alt != nil {
check.errorf(list.Pos(), "multiple methods named %s", name.Name)
// TODO(gri) provide correct declaration info and scope
// TODO(gri) with unified scopes (Scope, ObjSet) this can become
// just a normal declaration
obj := &Func{name.Pos(), check.pkg, nil, name.Name, sig, nil}
if alt := methods.Insert(obj); alt != nil && resolve {
// if !resolve, the parser complains
prevDecl := ""
if pos := alt.Pos(); pos.IsValid() {
prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", check.fset.Position(pos))
}
check.errorf(obj.Pos(), "%s redeclared in this block%s", obj.Name(), prevDecl)
}
if resolve {
check.register(name, obj)
}
}
} else {
@ -167,14 +185,14 @@ func (check *checker) tag(t *ast.BasicLit) string {
return ""
}
func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields []*Field, tags []string) {
func (check *checker) collectFields(scope *Scope, list *ast.FieldList, cycleOk bool) (fields []*Field, tags []string) {
if list == nil {
return
}
var typ Type // current field typ
var tag string // current field tag
add := func(name string, isAnonymous bool) {
add := func(field *ast.Field, ident *ast.Ident, name string, isAnonymous bool, pos token.Pos) {
// TODO(gri): rethink this - at the moment we allocate only a prefix
if tag != "" && tags == nil {
tags = make([]string, len(fields))
@ -182,6 +200,13 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [
if tags != nil {
tags = append(tags, tag)
}
if resolve {
fld := &Var{pos: pos, pkg: check.pkg, name: name, typ: typ, decl: field}
check.declare(scope, fld)
if resolve && ident != nil {
check.register(ident, fld)
}
}
fields = append(fields, &Field{check.pkg, name, typ, isAnonymous})
}
@ -191,18 +216,19 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [
if len(f.Names) > 0 {
// named fields
for _, name := range f.Names {
add(name.Name, false)
add(f, name, name.Name, false, name.Pos())
}
} else {
// anonymous field
pos := f.Type.Pos()
switch t := typ.Deref().(type) {
case *Basic:
add(t.name, true)
add(f, nil, t.name, true, pos)
case *Named:
add(t.obj.name, true)
add(f, nil, t.obj.name, true, pos)
default:
if typ != Typ[Invalid] {
check.invalidAST(f.Type.Pos(), "anonymous field type %s must be named", typ)
check.invalidAST(pos, "anonymous field type %s must be named", typ)
}
}
}
@ -870,6 +896,9 @@ func (check *checker) index(arg ast.Expr, length int64, iota int) (i int64, ok b
// compositeLitKey resolves unresolved composite literal keys.
// For details, see comment in go/parser/parser.go, method parseElement.
func (check *checker) compositeLitKey(key ast.Expr) {
if resolve {
return
}
if ident, ok := key.(*ast.Ident); ok && ident.Obj == nil {
if obj := check.pkg.scope.Lookup(ident.Name); obj != nil {
check.register(ident, obj)
@ -1043,15 +1072,37 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
goto Error // error was reported before
case *ast.Ident:
if e.Name == "_" {
if !resolve && e.Name == "_" {
check.invalidOp(e.Pos(), "cannot use _ as value or type")
goto Error
}
obj := check.lookup(e)
if obj == nil {
if resolve {
if e.Name == "_" {
check.invalidOp(e.Pos(), "cannot use _ as value or type")
} else {
// TODO(gri) anonymous function result parameters are
// not declared - this causes trouble when
// type-checking return statements
check.errorf(e.Pos(), "undeclared name: %s", e.Name)
}
}
goto Error // error was reported before
}
check.object(obj, cycleOk)
if resolve {
typ := obj.Type()
if check.objMap == nil {
if typ == nil {
check.dump("%s: %s not declared?", e.Pos(), e)
}
assert(typ != nil)
} else if typ == nil {
check.declareObject(obj, cycleOk)
}
} else {
check.object(obj, cycleOk)
}
switch obj := obj.(type) {
case *Package:
check.errorf(e.Pos(), "use of package %s not in selector", obj.Name)
@ -1073,7 +1124,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
case *TypeName:
x.mode = typexpr
if !cycleOk && obj.typ.Underlying() == nil {
check.errorf(obj.spec.Pos(), "illegal cycle in declaration of %s", obj.Name)
check.errorf(obj.spec.Pos(), "illegal cycle in declaration of %s", obj.name)
x.expr = e
x.typ = Typ[Invalid]
return // don't goto Error - need x.mode == typexpr
@ -1675,19 +1726,22 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
x.mode = typexpr
case *ast.StructType:
scope := &Scope{Outer: check.topScope}
fields, tags := check.collectFields(scope, e.Fields, cycleOk)
x.mode = typexpr
fields, tags := check.collectFields(e.Fields, cycleOk)
x.typ = &Struct{fields: fields, tags: tags}
x.typ = &Struct{scope: scope, fields: fields, tags: tags}
case *ast.FuncType:
params, isVariadic := check.collectParams(e.Params, true)
results, _ := check.collectParams(e.Results, false)
scope := &Scope{Outer: check.topScope}
params, isVariadic := check.collectParams(scope, e.Params, true)
results, _ := check.collectParams(scope, e.Results, false)
x.mode = typexpr
x.typ = &Signature{recv: nil, params: NewTuple(params...), results: NewTuple(results...), isVariadic: isVariadic}
x.typ = &Signature{scope: scope, recv: nil, params: NewTuple(params...), results: NewTuple(results...), isVariadic: isVariadic}
case *ast.InterfaceType:
scope := &Scope{Outer: check.topScope}
x.mode = typexpr
x.typ = &Interface{methods: check.collectMethods(e.Methods)}
x.typ = &Interface{methods: check.collectMethods(scope, e.Methods)}
case *ast.MapType:
x.mode = typexpr

View File

@ -590,7 +590,7 @@ func (p *gcParser) parseInterfaceType() Type {
}
pkg, name := p.parseName(true)
sig := p.parseSignature()
if alt := methods.Insert(&Func{pkg, name, sig, nil}); alt != nil {
if alt := methods.Insert(&Func{token.NoPos, pkg, nil, name, sig, nil}); alt != nil {
p.errorf("multiple methods named %s.%s", alt.Pkg().name, alt.Name())
}
}
@ -879,7 +879,7 @@ func (p *gcParser) parseMethodDecl() {
// add method to type unless type was imported before
// and method exists already
base.methods.Insert(&Func{pkg, name, sig, nil})
base.methods.Insert(&Func{token.NoPos, pkg, nil, name, sig, nil})
}
// FuncDecl = "func" ExportedName Func .

View File

@ -11,23 +11,29 @@ import (
"code.google.com/p/go.tools/go/exact"
)
// TODO(gri) provide a complete set of factory functions!
// An Object describes a named language entity such as a package,
// constant, type, variable, function (incl. methods), or label.
// All objects implement the Object interface.
//
type Object interface {
Pkg() *Package // nil for objects in the Universe scope
Scope() *Scope
Outer() *Scope // the scope in which this object is declared
Name() string
Type() Type
Pos() token.Pos
Pos() token.Pos // position of object identifier in declaration
// TODO(gri) provide String method!
setOuter(*Scope)
}
// A Package represents the contents (objects) of a Go package.
type Package struct {
pos token.Pos // position of package import path or local package identifier, if present
name string
path string // import path, "" for current (non-imported) package
path string // import path, "" for current (non-imported) package
outer *Scope
scope *Scope // package-level scope
imports map[string]*Package // map of import paths to imported packages
complete bool // if set, this package was imported completely
@ -40,35 +46,46 @@ func NewPackage(path, name string) *Package {
}
func (obj *Package) Pkg() *Package { return obj }
func (obj *Package) Outer() *Scope { return obj.outer }
func (obj *Package) Scope() *Scope { return obj.scope }
func (obj *Package) Name() string { return obj.name }
func (obj *Package) Type() Type { return Typ[Invalid] }
func (obj *Package) Pos() token.Pos {
if obj.spec == nil {
return token.NoPos
if obj.pos.IsValid() {
return obj.pos
}
return obj.spec.Pos()
if obj.spec != nil {
return obj.spec.Pos()
}
return token.NoPos
}
func (obj *Package) Path() string { return obj.path }
func (obj *Package) Imports() map[string]*Package { return obj.imports }
func (obj *Package) Complete() bool { return obj.complete }
func (obj *Package) setOuter(*Scope) { /* don't do anything - this is the package's scope */
}
// A Const represents a declared constant.
type Const struct {
pkg *Package
name string
typ Type
val exact.Value
pos token.Pos // position of identifier in constant declaration
pkg *Package
outer *Scope
name string
typ Type
val exact.Value
visited bool // for initialization cycle detection
spec *ast.ValueSpec
}
func (obj *Const) Pkg() *Package { return obj.pkg }
func (obj *Const) Scope() *Scope { panic("unimplemented") }
func (obj *Const) Outer() *Scope { return obj.outer }
func (obj *Const) Name() string { return obj.name }
func (obj *Const) Type() Type { return obj.typ }
func (obj *Const) Pos() token.Pos {
if obj.pos.IsValid() {
return obj.pos
}
if obj.spec == nil {
return token.NoPos
}
@ -79,51 +96,63 @@ func (obj *Const) Pos() token.Pos {
}
return token.NoPos
}
func (obj *Const) Val() exact.Value { return obj.val }
func (obj *Const) Val() exact.Value { return obj.val }
func (obj *Const) setOuter(s *Scope) { obj.outer = s }
// A TypeName represents a declared type.
type TypeName struct {
pkg *Package
name string
typ Type // *Named or *Basic
pos token.Pos // position of identifier in type declaration
pkg *Package
outer *Scope
name string
typ Type // *Named or *Basic
spec *ast.TypeSpec
}
func NewTypeName(pkg *Package, name string, typ Type) *TypeName {
return &TypeName{pkg, name, typ, nil}
return &TypeName{token.NoPos, pkg, nil, name, typ, nil}
}
func (obj *TypeName) Pkg() *Package { return obj.pkg }
func (obj *TypeName) Scope() *Scope { panic("unimplemented") }
func (obj *TypeName) Outer() *Scope { return obj.outer }
func (obj *TypeName) Name() string { return obj.name }
func (obj *TypeName) Type() Type { return obj.typ }
func (obj *TypeName) Pos() token.Pos {
if obj.pos.IsValid() {
return obj.pos
}
if obj.spec == nil {
return token.NoPos
}
return obj.spec.Pos()
}
func (obj *TypeName) setOuter(s *Scope) { obj.outer = s }
// A Variable represents a declared variable (including function parameters and results).
type Var struct {
pkg *Package // nil for parameters
name string
typ Type
pos token.Pos // position of identifier in variable declaration
pkg *Package // nil for parameters
outer *Scope
name string
typ Type
visited bool // for initialization cycle detection
decl interface{}
}
func NewVar(pkg *Package, name string, typ Type) *Var {
return &Var{pkg, name, typ, false, nil}
return &Var{token.NoPos, pkg, nil, name, typ, false, nil}
}
func (obj *Var) Pkg() *Package { return obj.pkg }
func (obj *Var) Scope() *Scope { panic("unimplemented") }
func (obj *Var) Outer() *Scope { return obj.outer }
func (obj *Var) Name() string { return obj.name }
func (obj *Var) Type() Type { return obj.typ }
func (obj *Var) Pos() token.Pos {
if obj.pos.IsValid() {
return obj.pos
}
switch d := obj.decl.(type) {
case *ast.Field:
for _, n := range d.Names {
@ -146,26 +175,33 @@ func (obj *Var) Pos() token.Pos {
}
return token.NoPos
}
func (obj *Var) setOuter(s *Scope) { obj.outer = s }
// A Func represents a declared function.
type Func struct {
pkg *Package
name string
typ Type // *Signature or *Builtin
pos token.Pos
pkg *Package
outer *Scope
name string
typ Type // *Signature or *Builtin
decl *ast.FuncDecl
}
func (obj *Func) Pkg() *Package { return obj.pkg }
func (obj *Func) Scope() *Scope { panic("unimplemented") }
func (obj *Func) Outer() *Scope { return obj.outer }
func (obj *Func) Name() string { return obj.name }
func (obj *Func) Type() Type { return obj.typ }
func (obj *Func) Pos() token.Pos {
if obj.pos.IsValid() {
return obj.pos
}
if obj.decl != nil && obj.decl.Name != nil {
return obj.decl.Name.Pos()
}
return token.NoPos
}
func (obj *Func) setOuter(s *Scope) { obj.outer = s }
// newObj returns a new Object for a given *ast.Object.
// It does not canonicalize them (it always returns a new one).

View File

@ -23,7 +23,7 @@ func (check *checker) declareObj(scope, altScope *Scope, obj Object, dotImport t
// for dot-imports, local declarations are declared first - swap messages
if dotImport.IsValid() {
if pos := alt.Pos(); pos.IsValid() {
check.errorf(pos, fmt.Sprintf("%s redeclared in this block by dot-import at %s",
check.errorf(pos, fmt.Sprintf("%s redeclared in this block by import at %s",
obj.Name(), check.fset.Position(dotImport)))
return
}
@ -50,25 +50,11 @@ func (check *checker) resolveIdent(scope *Scope, ident *ast.Ident) bool {
return false
}
func (check *checker) resolve(importer Importer) (methods []*ast.FuncDecl) {
func (check *checker) resolve(importer Importer, files []*ast.File) (methods []*ast.FuncDecl) {
pkg := check.pkg
// complete package scope
i := 0
for _, file := range check.files {
// package names must match
switch name := file.Name.Name; {
case pkg.name == "":
pkg.name = name
case name != pkg.name:
check.errorf(file.Package, "package %s; expected %s", name, pkg.name)
continue // ignore this file
}
// keep this file
check.files[i] = file
i++
for _, file := range files {
// the package identifier denotes the current package
check.register(file.Name, pkg)
@ -118,10 +104,9 @@ func (check *checker) resolve(importer Importer) (methods []*ast.FuncDecl) {
}
}
}
check.files = check.files[0:i]
// complete file scopes with imports and resolve identifiers
for _, file := range check.files {
for _, file := range files {
// build file scope by processing all imports
importErrors := false
fileScope := &Scope{Outer: pkg.scope}

661
go/types/resolver.go Normal file
View File

@ -0,0 +1,661 @@
// Copyright 2013 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.
package types
import (
"fmt"
"go/ast"
"go/token"
"strconv"
"code.google.com/p/go.tools/go/exact"
)
func (check *checker) declare(scope *Scope, obj Object) {
if obj.Name() == "_" {
obj.setOuter(scope)
return // blank identifiers are not visible
}
if alt := scope.Insert(obj); alt != nil {
prevDecl := ""
if pos := alt.Pos(); pos.IsValid() {
prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", check.fset.Position(pos))
}
check.errorf(obj.Pos(), "%s redeclared in this block%s", obj.Name(), prevDecl)
// TODO(gri) Instead, change this into two separate error messages (easier to handle by tools)
}
}
func (check *checker) declareShort(scope *Scope, list []Object) {
n := 0 // number of new objects
for _, obj := range list {
if obj.Name() == "_" {
obj.setOuter(scope)
continue // blank identifiers are not visible
}
if scope.Insert(obj) == nil {
n++ // new declaration
}
}
if n == 0 {
check.errorf(list[0].Pos(), "no new variables on left side of :=")
}
}
// A decl describes a package-level const, type, var, or func declaration.
type decl struct {
file *Scope // scope of file containing this declaration
typ ast.Expr // type, or nil
init ast.Expr // initialization expression, or nil
}
// An mdecl describes a method declaration.
type mdecl struct {
file *Scope // scope of file containing this declaration
meth *ast.FuncDecl
}
// A projExpr projects the index'th value of a multi-valued expression.
// projExpr implements ast.Expr.
type projExpr struct {
lhs []*Var // all variables on the lhs
ast.Expr // rhs
}
func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
pkg := check.pkg
// Phase 1: Pre-declare all package scope objects so that they can be found
// when type-checking package objects.
var scopes []*Scope // corresponding file scope per file
var objList []Object
var objMap = make(map[Object]*decl)
var methods []*mdecl
var fileScope *Scope // current file scope, used by add
add := func(obj Object, typ, init ast.Expr) {
objList = append(objList, obj)
objMap[obj] = &decl{fileScope, typ, init}
// TODO(gri) move check.declare call here
}
for _, file := range files {
// the package identifier denotes the current package, but it is in no scope
check.register(file.Name, pkg)
fileScope = &Scope{Outer: pkg.scope}
scopes = append(scopes, fileScope)
for _, decl := range file.Decls {
switch d := decl.(type) {
case *ast.BadDecl:
// ignore
case *ast.GenDecl:
var last *ast.ValueSpec // last list of const initializers seen
for iota, spec := range d.Specs {
switch s := spec.(type) {
case *ast.ImportSpec:
if importer == nil {
//importErrors = true
continue
}
path, _ := strconv.Unquote(s.Path.Value)
imp, err := importer(pkg.imports, path)
if err != nil {
check.errorf(s.Path.Pos(), "could not import %s (%s)", path, err)
//importErrors = true
continue
}
// TODO(gri) If a local package name != "." is provided,
// we could proceed even if the import failed. Consider
// adjusting the logic here a bit.
// local name overrides imported package name
name := imp.name
if s.Name != nil {
name = s.Name.Name
}
// add import to file scope
if name == "." {
// merge imported scope with file scope
for _, obj := range imp.scope.Entries {
// gcimported package scopes contain non-exported
// objects such as types used in partially exported
// objects - do not accept them
if ast.IsExported(obj.Name()) {
// Note: This will change each imported object's scope!
// May be an issue for types aliases.
check.declare(fileScope, obj)
}
}
// TODO(gri) consider registering the "." identifier
// if we have Context.Ident callbacks for say blank
// (_) identifiers
// check.register(spec.Name, pkg)
} else if name != "_" {
// declare imported package object in file scope
// (do not re-use imp in the file scope but create
// a new object instead; the Decl field is different
// for different files)
obj := &Package{name: name, scope: imp.scope, spec: s}
check.declare(fileScope, obj)
}
case *ast.ValueSpec:
switch d.Tok {
case token.CONST:
// determine which initialization expressions to use
if len(s.Values) > 0 {
last = s
}
// declare all constants
for i, name := range s.Names {
obj := &Const{pos: name.Pos(), pkg: pkg, name: name.Name, val: exact.MakeInt64(int64(iota)), spec: s}
check.declare(pkg.scope, obj)
check.register(name, obj)
var init ast.Expr
if i < len(last.Values) {
init = last.Values[i]
}
add(obj, last.Type, init)
}
// arity of lhs and rhs must match
if lhs, rhs := len(s.Names), len(s.Values); rhs > 0 {
switch {
case lhs < rhs:
// TODO(gri) once resolve is the default, use first message
// x := s.Values[lhs]
// check.errorf(x.Pos(), "too many initialization expressions")
check.errorf(s.Names[0].Pos(), "assignment count mismatch")
case lhs > rhs && rhs != 1:
// TODO(gri) once resolve is the default, use first message
// n := s.Names[rhs]
// check.errorf(n.Pos(), "missing initialization expression for %s", n)
check.errorf(s.Names[0].Pos(), "assignment count mismatch")
}
}
case token.VAR:
// declare all variables
lhs := make([]*Var, len(s.Names))
for i, name := range s.Names {
obj := &Var{pos: name.Pos(), pkg: pkg, name: name.Name, decl: s}
lhs[i] = obj
check.declare(pkg.scope, obj)
check.register(name, obj)
var init ast.Expr
switch len(s.Values) {
case len(s.Names):
// lhs and rhs match
init = s.Values[i]
case 1:
// rhs must be a multi-valued expression
init = &projExpr{lhs, s.Values[0]}
default:
if i < len(s.Values) {
init = s.Values[i]
}
}
add(obj, s.Type, init)
}
// arity of lhs and rhs must match
if lhs, rhs := len(s.Names), len(s.Values); rhs > 0 {
switch {
case lhs < rhs:
// TODO(gri) once resolve is the default, use first message
// x := s.Values[lhs]
// check.errorf(x.Pos(), "too many initialization expressions")
check.errorf(s.Names[0].Pos(), "assignment count mismatch")
case lhs > rhs && rhs != 1:
// TODO(gri) once resolve is the default, use first message
// n := s.Names[rhs]
// check.errorf(n.Pos(), "missing initialization expression for %s", n)
check.errorf(s.Names[0].Pos(), "assignment count mismatch")
}
}
default:
check.invalidAST(s.Pos(), "invalid token %s", d.Tok)
}
case *ast.TypeSpec:
obj := &TypeName{pos: s.Name.Pos(), pkg: pkg, name: s.Name.Name, spec: s}
check.declare(pkg.scope, obj)
add(obj, s.Type, nil)
check.register(s.Name, obj)
default:
check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s)
}
}
case *ast.FuncDecl:
if d.Recv != nil {
// collect method
methods = append(methods, &mdecl{fileScope, d})
continue
}
obj := &Func{pos: d.Name.Pos(), pkg: pkg, name: d.Name.Name, decl: d}
if obj.name == "init" {
// init functions are not visible - don't declare them in package scope
obj.outer = pkg.scope
} else {
check.declare(pkg.scope, obj)
}
check.register(d.Name, obj)
add(obj, nil, nil)
default:
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
}
}
}
// Phase 2: Objects in file scopes and package scopes must have different names.
for _, scope := range scopes {
for _, obj := range scope.Entries {
if alt := pkg.scope.Lookup(obj.Name()); alt != nil {
// TODO(gri) better error message
check.errorf(alt.Pos(), "%s redeclared in this block by import of package %s", obj.Name(), obj.Pkg().Name())
}
}
}
// Phase 3: Associate methods with types.
// We do this after all top-level type names have been collected.
check.topScope = pkg.scope
for _, meth := range methods {
m := meth.meth
// The receiver type must be one of the following:
// - *ast.Ident
// - *ast.StarExpr{*ast.Ident}
// - *ast.BadExpr (parser error)
typ := m.Recv.List[0].Type
if ptr, ok := typ.(*ast.StarExpr); ok {
typ = ptr.X
}
// determine receiver base type name
ident, ok := typ.(*ast.Ident)
if !ok {
// Disabled for now since the parser reports this error.
// check.errorf(typ.Pos(), "receiver base type must be an (unqualified) identifier")
continue // ignore this method
}
// determine receiver base type object
var tname *TypeName
if obj := check.lookup(ident); obj != nil {
obj, ok := obj.(*TypeName)
if !ok {
check.errorf(ident.Pos(), "%s is not a type", ident.Name)
continue // ignore this method
}
if obj.pkg != pkg {
check.errorf(ident.Pos(), "cannot define method on non-local type %s", ident.Name)
continue // ignore this method
}
tname = obj
} else {
// identifier not declared/resolved
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
continue // ignore this method
}
// declare method in receiver base type scope
scope := check.methods[tname]
if scope == nil {
scope = new(Scope)
check.methods[tname] = scope
}
fun := &Func{pos: m.Name.Pos(), pkg: check.pkg, name: m.Name.Name, decl: m}
check.declare(scope, fun)
check.register(m.Name, fun)
// HACK(gri) change method outer scope to file scope containing the declaration
fun.outer = meth.file // remember the file scope
}
// Phase 4) Typecheck all objects in objList but not function bodies.
check.objMap = objMap // indicate we are doing global declarations (objects may not have a type yet)
for _, obj := range objList {
if obj.Type() == nil {
check.declareObject(obj, false)
}
}
check.objMap = nil // done with global declarations
// Phase 5) Typecheck all functions.
// - done by the caller for now
}
func (check *checker) declareObject(obj Object, cycleOk bool) {
d := check.objMap[obj]
// adjust file scope for current object
oldScope := check.topScope
check.topScope = d.file // for lookup
switch obj := obj.(type) {
case *Const:
check.declareConst(obj, d.typ, d.init)
case *Var:
check.declareVar(obj, d.typ, d.init)
case *TypeName:
check.declareType(obj, d.typ, cycleOk)
case *Func:
check.declareFunc(obj, cycleOk)
default:
unreachable()
}
check.topScope = oldScope
}
func (check *checker) declareConst(obj *Const, typ, init ast.Expr) {
if obj.visited {
check.errorf(obj.Pos(), "illegal cycle in initialization of constant %s", obj.name)
obj.typ = Typ[Invalid]
return
}
obj.visited = true
iota, ok := exact.Int64Val(obj.val) // set in phase 1
assert(ok)
// obj.val = exact.MakeUnknown() //do we need this? should we set val to nil?
// determine type, if any
if typ != nil {
obj.typ = check.typ(typ, false)
}
var x operand
if init == nil {
// TODO(gri) enable error message once resolve is default
// check.errorf(obj.Pos(), "missing initialization expression for %s", obj.name)
goto Error
}
check.expr(&x, init, nil, int(iota))
if x.mode == invalid {
goto Error
}
check.assign(obj, &x)
return
Error:
if obj.typ == nil {
obj.typ = Typ[Invalid]
} else {
obj.val = exact.MakeUnknown()
}
}
func (check *checker) declareVar(obj *Var, typ, init ast.Expr) {
if obj.visited {
check.errorf(obj.Pos(), "illegal cycle in initialization of variable %s", obj.name)
obj.typ = Typ[Invalid]
return
}
obj.visited = true
// determine type, if any
if typ != nil {
obj.typ = check.typ(typ, false)
}
if init == nil {
if typ == nil {
// TODO(gri) enable error message once resolve is default
// check.errorf(obj.Pos(), "missing type or initialization expression for %s", obj.name)
obj.typ = Typ[Invalid]
}
return
}
// unpack projection expression, if any
proj, multi := init.(*projExpr)
if multi {
init = proj.Expr
}
var x operand
check.expr(&x, init, nil, -1)
if x.mode == invalid {
goto Error
}
if multi {
if t, ok := x.typ.(*Tuple); ok && len(proj.lhs) == t.Len() {
// function result
x.mode = value
for i, lhs := range proj.lhs {
x.expr = nil // TODO(gri) should do better here
x.typ = t.At(i).typ
check.assign(lhs, &x)
}
return
}
if x.mode == valueok && len(proj.lhs) == 2 {
// comma-ok expression
x.mode = value
check.assign(proj.lhs[0], &x)
x.typ = Typ[UntypedBool]
check.assign(proj.lhs[1], &x)
return
}
// TODO(gri) better error message
check.errorf(proj.lhs[0].Pos(), "assignment count mismatch")
goto Error
}
check.assign(obj, &x)
return
Error:
// mark all involved variables so we can avoid repeated error messages
if multi {
for _, obj := range proj.lhs {
if obj.typ == nil {
obj.typ = Typ[Invalid]
obj.visited = true
}
}
} else if obj.typ == nil {
obj.typ = Typ[Invalid]
}
return
}
func (check *checker) declareType(obj *TypeName, typ ast.Expr, cycleOk bool) {
named := &Named{obj: obj}
obj.typ = named // mark object so recursion terminates in case of cycles
named.underlying = check.typ(typ, cycleOk).Underlying()
// typecheck associated method signatures
if scope := check.methods[obj]; scope != nil {
switch t := named.underlying.(type) {
case *Struct:
// struct fields must not conflict with methods
for _, f := range t.fields {
if m := scope.Lookup(f.Name); m != nil {
check.errorf(m.Pos(), "type %s has both field and method named %s", obj.name, f.Name)
// ok to continue
}
}
case *Interface:
// methods cannot be associated with an interface type
for _, m := range scope.Entries {
recv := m.(*Func).decl.Recv.List[0].Type
check.errorf(recv.Pos(), "invalid receiver type %s (%s is an interface type)", obj.name, obj.name)
// ok to continue
}
}
// typecheck method signatures
var methods ObjSet
for _, obj := range scope.Entries {
m := obj.(*Func)
// set the correct file scope for checking this method type
fileScope := m.outer
assert(fileScope != nil)
oldScope := check.topScope
check.topScope = fileScope
sig := check.typ(m.decl.Type, cycleOk).(*Signature)
params, _ := check.collectParams(sig.scope, m.decl.Recv, false)
check.topScope = oldScope // reset topScope
sig.recv = params[0] // the parser/assocMethod ensure there is exactly one parameter
m.typ = sig
assert(methods.Insert(obj) == nil)
check.later(m, sig, m.decl.Body)
}
named.methods = methods
delete(check.methods, obj) // we don't need this scope anymore
}
}
func (check *checker) declareFunc(obj *Func, cycleOk bool) {
fdecl := obj.decl
// methods are typechecked when their receivers are typechecked
// TODO(gri) there is no reason to make this a special case: receivers are simply parameters
if fdecl.Recv == nil {
sig := check.typ(fdecl.Type, cycleOk).(*Signature)
if obj.name == "init" && (sig.params.Len() > 0 || sig.results.Len() > 0) {
check.errorf(fdecl.Pos(), "func init must have no arguments and no return values")
// ok to continue
}
obj.typ = sig
check.later(obj, sig, fdecl.Body)
}
}
func (check *checker) declStmt(decl ast.Decl) {
pkg := check.pkg
switch d := decl.(type) {
case *ast.BadDecl:
// ignore
case *ast.GenDecl:
var last []ast.Expr // last list of const initializers seen
for iota, spec := range d.Specs {
switch s := spec.(type) {
case *ast.ValueSpec:
switch d.Tok {
case token.CONST:
// determine which initialization expressions to use
if len(s.Values) > 0 {
last = s.Values
}
// declare all constants
lhs := make([]*Const, len(s.Names))
for i, name := range s.Names {
obj := &Const{pos: name.Pos(), pkg: pkg, name: name.Name, val: exact.MakeInt64(int64(iota)), spec: s}
lhs[i] = obj
var init ast.Expr
if i < len(last) {
init = last[i]
}
check.declareConst(obj, s.Type, init)
check.register(name, obj)
}
// arity of lhs and rhs must match
switch lhs, rhs := len(s.Names), len(last); {
case lhs < rhs:
x := last[lhs]
check.errorf(x.Pos(), "too many initialization expressions")
case lhs > rhs:
n := s.Names[rhs]
check.errorf(n.Pos(), "missing initialization expression for %s", n)
}
for _, obj := range lhs {
check.declare(check.topScope, obj)
}
case token.VAR:
// declare all variables
lhs := make([]*Var, len(s.Names))
for i, name := range s.Names {
obj := &Var{pos: name.Pos(), pkg: pkg, name: name.Name, decl: s}
lhs[i] = obj
check.register(name, obj)
}
// iterate in 2 phases because declareVar requires fully initialized lhs!
for i, obj := range lhs {
var init ast.Expr
switch len(s.Values) {
case len(s.Names):
// lhs and rhs match
init = s.Values[i]
case 1:
// rhs must be a multi-valued expression
init = &projExpr{lhs, s.Values[0]}
default:
if i < len(s.Values) {
init = s.Values[i]
}
}
check.declareVar(obj, s.Type, init)
}
// arity of lhs and rhs must match
// TODO(gri) Disabled for now to match existing test errors.
// These error messages are better than what we have now.
/*
if lhs, rhs := len(s.Names), len(s.Values); rhs > 0 {
switch {
case lhs < rhs:
x := s.Values[lhs]
check.errorf(x.Pos(), "too many initialization expressions")
case lhs > rhs && rhs != 1:
n := s.Names[rhs]
check.errorf(n.Pos(), "missing initialization expression for %s", n)
}
}
*/
for _, obj := range lhs {
check.declare(check.topScope, obj)
}
default:
check.invalidAST(s.Pos(), "invalid token %s", d.Tok)
}
case *ast.TypeSpec:
obj := &TypeName{pos: s.Name.Pos(), pkg: pkg, name: s.Name.Name, spec: s}
check.declare(check.topScope, obj)
check.declareType(obj, s.Type, false)
check.register(s.Name, obj)
default:
check.invalidAST(s.Pos(), "const, type, or var declaration expected")
}
}
default:
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
}
}

View File

@ -55,7 +55,11 @@ func TestResolveQualifiedIdents(t *testing.T) {
fset := token.NewFileSet()
var files []*ast.File
for _, src := range sources {
f, err := parser.ParseFile(fset, "", src, parser.DeclarationErrors)
mode := parser.AllErrors
if !resolve {
mode |= parser.DeclarationErrors
}
f, err := parser.ParseFile(fset, "", src, mode)
if err != nil {
t.Fatal(err)
}
@ -79,9 +83,11 @@ func TestResolveQualifiedIdents(t *testing.T) {
}
// check that there are no top-level unresolved identifiers
for _, f := range files {
for _, x := range f.Unresolved {
t.Errorf("%s: unresolved global identifier %s", fset.Position(x.Pos()), x.Name)
if !resolve {
for _, f := range files {
for _, x := range f.Unresolved {
t.Errorf("%s: unresolved global identifier %s", fset.Position(x.Pos()), x.Name)
}
}
}
@ -113,6 +119,9 @@ func TestResolveQualifiedIdents(t *testing.T) {
ast.Inspect(f, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.StructType:
if resolve {
break // nothing to do
}
for _, list := range x.Fields.List {
for _, f := range list.Names {
assert(idents[f] == nil)
@ -120,6 +129,9 @@ func TestResolveQualifiedIdents(t *testing.T) {
}
}
case *ast.InterfaceType:
if resolve {
break // nothing to do
}
for _, list := range x.Methods.List {
for _, f := range list.Names {
assert(idents[f] == nil)

View File

@ -38,7 +38,8 @@ func (s *Scope) Lookup(name string) Object {
// Insert attempts to insert an object obj into scope s.
// If s already contains an object with the same name,
// Insert leaves s unchanged and returns that object.
// Otherwise it inserts obj and returns nil.
// Otherwise it inserts obj, sets the object's scope to
// s, and returns nil.
//
func (s *Scope) Insert(obj Object) Object {
name := obj.Name()
@ -46,6 +47,7 @@ func (s *Scope) Insert(obj Object) Object {
return alt
}
s.Entries = append(s.Entries, obj)
obj.setOuter(s)
// If the scope size reaches a threshold, use a map for faster lookups.
const threshold = 20

View File

@ -30,6 +30,10 @@ var (
)
func TestStdlib(t *testing.T) {
if resolve {
fmt.Println("*** Running new code: Identifiers are resolved by type checker. ***")
}
walkDirs(t, filepath.Join(runtime.GOROOT(), "src/pkg"))
if *verbose {
fmt.Println(pkgCount, "packages typechecked in", time.Since(start))
@ -48,7 +52,11 @@ func typecheck(t *testing.T, path string, filenames []string) {
// parse package files
var files []*ast.File
for _, filename := range filenames {
file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors|parser.AllErrors)
mode := parser.AllErrors
if !resolve {
mode |= parser.DeclarationErrors
}
file, err := parser.ParseFile(fset, filename, nil, mode)
if err != nil {
// the parser error may be a list of individual errors; report them all
if list, ok := err.(scanner.ErrorList); ok {

View File

@ -41,7 +41,9 @@ func (check *checker) assignment(x *operand, to Type) bool {
// if its type is not set, it is deduced from the type of x or set to Typ[Invalid] in
// case of an error.
//
func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota int) {
func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota int, isConst bool) {
assert(!isConst || decl)
// Start with rhs so we have an expression type
// for declarations with implicit type.
if x == nil {
@ -68,7 +70,7 @@ func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota
var z operand
check.expr(&z, lhs, nil, -1)
if z.mode == invalid {
if z.mode == invalid || z.typ == Typ[Invalid] {
return
}
@ -91,8 +93,22 @@ func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota
// Determine typ of lhs: If the object doesn't have a type
// yet, determine it from the type of x; if x is invalid,
// set the object type to Typ[Invalid].
var obj Object
var typ Type
obj := check.lookup(ident)
if resolve {
if isConst {
obj = &Const{pos: ident.Pos(), pkg: check.pkg, name: ident.Name}
} else {
obj = &Var{pos: ident.Pos(), pkg: check.pkg, name: ident.Name}
}
check.register(ident, obj)
defer check.declare(check.topScope, obj)
} else {
obj = check.lookup(ident)
}
switch obj := obj.(type) {
default:
unreachable()
@ -165,14 +181,15 @@ func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota
// of the corresponding rhs expressions, or set to Typ[Invalid] in case of an error.
// Precondition: len(lhs) > 0 .
//
func (check *checker) assignNtoM(lhs, rhs []ast.Expr, decl bool, iota int) {
func (check *checker) assignNtoM(lhs, rhs []ast.Expr, decl bool, iota int, isConst bool) {
assert(len(lhs) > 0)
assert(!isConst || decl)
// If the lhs and rhs have corresponding expressions, treat each
// matching pair as an individual pair.
if len(lhs) == len(rhs) {
for i, e := range rhs {
check.assign1to1(lhs[i], e, nil, decl, iota)
check.assign1to1(lhs[i], e, nil, decl, iota, isConst)
}
return
}
@ -197,7 +214,7 @@ func (check *checker) assignNtoM(lhs, rhs []ast.Expr, decl bool, iota int) {
obj := t.At(i)
x.expr = nil // TODO(gri) should do better here
x.typ = obj.typ
check.assign1to1(lhs[i], nil, &x, decl, iota)
check.assign1to1(lhs[i], nil, &x, decl, iota, isConst)
}
return
}
@ -205,10 +222,10 @@ func (check *checker) assignNtoM(lhs, rhs []ast.Expr, decl bool, iota int) {
if x.mode == valueok && len(lhs) == 2 {
// comma-ok expression
x.mode = value
check.assign1to1(lhs[0], nil, &x, decl, iota)
check.assign1to1(lhs[0], nil, &x, decl, iota, isConst)
x.typ = Typ[UntypedBool]
check.assign1to1(lhs[1], nil, &x, decl, iota)
check.assign1to1(lhs[1], nil, &x, decl, iota, isConst)
return
}
}
@ -224,7 +241,21 @@ Error:
check.errorf(e.Pos(), "cannot declare %s", e)
continue
}
switch obj := check.lookup(ident).(type) {
var obj Object
if resolve {
if isConst {
obj = &Const{pos: ident.Pos(), pkg: check.pkg, name: ident.Name}
} else {
obj = &Var{pos: ident.Pos(), pkg: check.pkg, name: ident.Name}
}
defer check.declare(check.topScope, obj)
} else {
obj = check.lookup(ident)
}
switch obj := obj.(type) {
case *Const:
obj.typ = Typ[Invalid]
case *Var:
@ -287,6 +318,11 @@ func (check *checker) stmt(s ast.Stmt) {
// ignore
case *ast.DeclStmt:
if resolve {
check.declStmt(s.Decl)
return
}
d, _ := s.Decl.(*ast.GenDecl)
if d == nil || (d.Tok != token.CONST && d.Tok != token.TYPE && d.Tok != token.VAR) {
check.invalidAST(token.NoPos, "const, type, or var declaration expected")
@ -298,7 +334,7 @@ func (check *checker) stmt(s ast.Stmt) {
check.decl(d)
case *ast.LabeledStmt:
// TODO(gri) anything to do with label itself?
// TODO(gri) Declare label in the respectice label scope; define Label object.
check.stmt(s.Stmt)
case *ast.ExprStmt:
@ -363,7 +399,7 @@ func (check *checker) stmt(s ast.Stmt) {
if x.mode == invalid {
return
}
check.assign1to1(s.X, nil, &x, false, -1)
check.assign1to1(s.X, nil, &x, false, -1, false)
case *ast.AssignStmt:
switch s.Tok {
@ -372,7 +408,34 @@ func (check *checker) stmt(s ast.Stmt) {
check.invalidAST(s.Pos(), "missing lhs in assignment")
return
}
check.assignNtoM(s.Lhs, s.Rhs, s.Tok == token.DEFINE, -1)
if resolve && s.Tok == token.DEFINE {
// short variable declaration
lhs := make([]Object, len(s.Lhs))
for i, x := range s.Lhs {
var obj *Var
if ident, ok := x.(*ast.Ident); ok {
obj = &Var{pos: ident.Pos(), pkg: check.pkg, name: ident.Name, decl: s}
// If the variable is already declared (redeclaration in :=),
// register the identifier with the existing variable.
if alt := check.topScope.Lookup(ident.Name); alt != nil {
check.register(ident, alt)
} else {
check.register(ident, obj)
}
} else {
check.errorf(x.Pos(), "cannot declare %s", x)
// create a dummy variable
obj = &Var{pos: x.Pos(), pkg: check.pkg, name: "_", decl: s}
}
lhs[i] = obj
}
check.assignMulti(lhs, s.Rhs)
check.declareShort(check.topScope, lhs) // scope starts after the assignment
} else {
check.assignNtoM(s.Lhs, s.Rhs, s.Tok == token.DEFINE, -1, false)
}
default:
// assignment operations
if len(s.Lhs) != 1 || len(s.Rhs) != 1 {
@ -413,7 +476,7 @@ func (check *checker) stmt(s ast.Stmt) {
if x.mode == invalid {
return
}
check.assign1to1(s.Lhs[0], nil, &x, false, -1)
check.assign1to1(s.Lhs[0], nil, &x, false, -1, false)
}
case *ast.GoStmt:
@ -425,6 +488,22 @@ func (check *checker) stmt(s ast.Stmt) {
case *ast.ReturnStmt:
sig := check.funcsig
if n := sig.results.Len(); n > 0 {
if resolve {
// determine if the function has named results
named := false
lhs := make([]Object, len(sig.results.vars))
for i, res := range sig.results.vars {
if res.name != "" {
// a blank (_) result parameter is a named result
named = true
}
lhs[i] = res
}
if len(s.Results) > 0 || !named {
check.assignMulti(lhs, s.Results)
return
}
}
// TODO(gri) should not have to compute lhs, named every single time - clean this up
lhs := make([]ast.Expr, n)
named := false // if set, function has named results
@ -440,7 +519,7 @@ func (check *checker) stmt(s ast.Stmt) {
}
if len(s.Results) > 0 || !named {
// TODO(gri) assignNtoM should perhaps not require len(lhs) > 0
check.assignNtoM(lhs, s.Results, false, -1)
check.assignNtoM(lhs, s.Results, false, -1, false)
}
} else if len(s.Results) > 0 {
check.errorf(s.Pos(), "no result values expected")
@ -450,9 +529,12 @@ func (check *checker) stmt(s ast.Stmt) {
// TODO(gri) implement this
case *ast.BlockStmt:
check.openScope()
check.stmtList(s.List)
check.closeScope()
case *ast.IfStmt:
check.openScope()
check.optionalStmt(s.Init)
var x operand
check.expr(&x, s.Cond, nil, -1)
@ -461,8 +543,10 @@ func (check *checker) stmt(s ast.Stmt) {
}
check.stmt(s.Body)
check.optionalStmt(s.Else)
check.closeScope()
case *ast.SwitchStmt:
check.openScope()
check.optionalStmt(s.Init)
var x operand
tag := s.Tag
@ -523,10 +607,14 @@ func (check *checker) stmt(s ast.Stmt) {
}
}
}
check.openScope()
check.stmtList(clause.Body)
check.closeScope()
}
check.closeScope()
case *ast.TypeSwitchStmt:
check.openScope()
check.optionalStmt(s.Init)
// A type switch guard must be of the form:
@ -552,7 +640,13 @@ func (check *checker) stmt(s ast.Stmt) {
check.invalidAST(s.Pos(), "incorrect form of type switch guard")
return
}
lhs = check.lookup(ident).(*Var)
if resolve {
// TODO(gri) in the future, create one of these for each block with the correct type!
lhs = &Var{pkg: check.pkg, name: ident.Name}
check.register(ident, lhs)
} else {
lhs = check.lookup(ident).(*Var)
}
rhs = guard.Rhs[0]
default:
check.invalidAST(s.Pos(), "incorrect form of type switch guard")
@ -576,6 +670,10 @@ func (check *checker) stmt(s ast.Stmt) {
return
}
if resolve && lhs != nil {
check.declare(check.topScope, lhs)
}
check.multipleDefaults(s.Body.List)
for _, s := range s.Body.List {
clause, _ := s.(*ast.CaseClause)
@ -608,7 +706,9 @@ func (check *checker) stmt(s ast.Stmt) {
}
lhs.typ = typ
}
check.openScope()
check.stmtList(clause.Body)
check.closeScope()
}
// There is only one object (lhs) associated with a lhs identifier, but that object
@ -617,6 +717,7 @@ func (check *checker) stmt(s ast.Stmt) {
if lhs != nil {
lhs.typ = x.typ
}
check.closeScope()
case *ast.SelectStmt:
check.multipleDefaults(s.Body.List)
@ -625,11 +726,14 @@ func (check *checker) stmt(s ast.Stmt) {
if clause == nil {
continue // error reported before
}
check.openScope()
check.optionalStmt(clause.Comm) // TODO(gri) check correctness of c.Comm (must be Send/RecvStmt)
check.stmtList(clause.Body)
check.closeScope()
}
case *ast.ForStmt:
check.openScope()
check.optionalStmt(s.Init)
if s.Cond != nil {
var x operand
@ -640,8 +744,10 @@ func (check *checker) stmt(s ast.Stmt) {
}
check.optionalStmt(s.Post)
check.stmt(s.Body)
check.closeScope()
case *ast.RangeStmt:
check.openScope()
// check expression to iterate over
decl := s.Tok == token.DEFINE
var x operand
@ -706,7 +812,7 @@ func (check *checker) stmt(s ast.Stmt) {
if s.Key != nil {
x.typ = key
x.expr = s.Key
check.assign1to1(s.Key, nil, &x, decl, -1)
check.assign1to1(s.Key, nil, &x, decl, -1, false)
} else {
check.invalidAST(s.Pos(), "range clause requires index iteration variable")
// ok to continue
@ -714,10 +820,11 @@ func (check *checker) stmt(s ast.Stmt) {
if s.Value != nil {
x.typ = val
x.expr = s.Value
check.assign1to1(s.Value, nil, &x, decl, -1)
check.assign1to1(s.Value, nil, &x, decl, -1, false)
}
check.stmt(s.Body)
check.closeScope()
default:
check.errorf(s.Pos(), "invalid statement")

View File

@ -42,6 +42,7 @@ const (
ui16 = ui2 & ui3
ui17 = ui2 | ui3
ui18 = ui2 ^ ui3
ui19 = 1 /* ERROR "invalid operation" */ % 1.0
// floating point values
uf0 = 0.
@ -179,6 +180,8 @@ const (
const (
a1, a2, a3 = 7, 3.1415926, "foo"
b1, b2, b3 = b3, b1, 42
c1 /* ERROR "assignment count mismatch" */, c2, c3 = 1, 2
d1 /* ERROR "assignment count mismatch" */, d2, d3 = 1, 2, 3, 4
_p0 = assert(a1 == 7)
_p1 = assert(a2 == 3.1415926)
_p2 = assert(a3 == "foo")

View File

@ -21,7 +21,7 @@ type flag int
type _ reflect /* ERROR "not exported" */ .flag
// dot-imported exported objects may conflict with local objects
type Value /* ERROR "redeclared in this block by dot-import" */ struct{}
type Value /* ERROR "redeclared in this block by import" */ struct{}
const pi = 3.1415
@ -74,7 +74,8 @@ type (
// is done in the parser and the other one in the type checker. We cannot
// easily avoid this w/o losing the check or functionality. This will be
// fixed once all resolution is done in the type checker.
a /* ERROR "redeclared" */ /* ERROR "redeclared" */ int
// TODO(gri) Disabled for now since this needs to work with the old and new resolver.
// a /* ERROR "redeclared" */ /* ERROR "redeclared" */ int
// where the cycle error appears depends on the
// order in which declarations are processed
@ -142,7 +143,7 @@ type (
I2 interface {
m1()
}
I3 interface { /* ERROR "multiple methods named m1" */
I3 interface {
m1()
m1 /* ERROR "redeclared" */ ()
}

View File

@ -110,7 +110,7 @@ func f1(a /* ERROR "not a type" */) {}
func f2(a, b, c d /* ERROR "not a type" */) {}
func f3() int { return 0 }
func f4() a /* ERROR "not a type" */ { return 0 /* ERROR "cannot convert" */ }
func f4() a /* ERROR "not a type" */ { return 0 }
func f5() (a, b, c d /* ERROR "not a type" */) { return }
func f6(a, b, c int) complex128 { return 0 }

View File

@ -61,9 +61,9 @@ func (T5 /* ERROR "invalid receiver" */) m2() {}
// Methods associated with non-local or unnamed types.
func (int /* ERROR "non-local type" */ ) m() {}
func ([ /* ERROR "expected" */ ]int) m() {}
func (time /* ERROR "expected" */ .Time) m() {}
func (x interface /* ERROR "expected" */ {}) m() {}
func ([ /* ERROR "identifier" */ ]int) m() {}
func (time /* ERROR "identifier" */ .Time) m() {}
func (x interface /* ERROR "identifier" */ {}) m() {}
// Double declarations across package files
const c_double = 0

View File

@ -6,9 +6,12 @@
package decls2
import "io"
const pi = 3.1415
func (T1) m /* ERROR "redeclared" */ () {}
func (T2) m(io.Writer) {}
type T3 struct {
f *T3

View File

@ -6,6 +6,9 @@ package types
import "go/ast"
// TODO(gri) Separate struct fields below into transient (used during type checking only)
// and permanent fields.
// A Type represents a type of Go.
// All types implement the Type interface.
type Type interface {
@ -137,13 +140,14 @@ type Field struct {
// A Struct represents a struct type.
type Struct struct {
scope *Scope
fields []*Field
tags []string // field tags; nil of there are no tags
offsets []int64 // field offsets in bytes, lazily computed
}
func NewStruct(fields []*Field, tags []string) *Struct {
return &Struct{fields: fields, tags: tags}
return &Struct{scope: nil, fields: fields, tags: tags}
}
func (s *Struct) NumFields() int { return len(s.fields) }
@ -230,6 +234,7 @@ func (t *Tuple) ForEach(f func(*Var)) {
// A Signature represents a (non-builtin) function type.
type Signature struct {
scope *Scope // function scope
recv *Var // nil if not a method
params *Tuple // (incoming) parameters from left to right; or nil
results *Tuple // (outgoing) results from left to right; or nil
@ -240,7 +245,7 @@ type Signature struct {
// and results, either of which may be nil. If isVariadic is set, the function
// is variadic.
func NewSignature(recv *Var, params, results *Tuple, isVariadic bool) *Signature {
return &Signature{recv, params, results, isVariadic}
return &Signature{nil, recv, params, results, isVariadic}
}
// Recv returns the receiver of signature s, or nil.

View File

@ -16,7 +16,11 @@ import (
const filename = "<src>"
func makePkg(t *testing.T, src string) (*Package, error) {
file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
mode := parser.AllErrors
if !resolve {
mode |= parser.DeclarationErrors
}
file, err := parser.ParseFile(fset, filename, src, mode)
if err != nil {
return nil, err
}
@ -154,7 +158,11 @@ var testExprs = []testEntry{
func TestExprs(t *testing.T) {
for _, test := range testExprs {
src := "package p; var _ = " + test.src + "; var (x, y int; s []string; f func(int, float32) int; i interface{}; t interface { foo() })"
file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
mode := parser.AllErrors
if !resolve {
mode |= parser.DeclarationErrors
}
file, err := parser.ParseFile(fset, filename, src, mode)
if err != nil {
t.Errorf("%s: %s", src, err)
continue

View File

@ -8,6 +8,7 @@ package types
import (
"go/ast"
"go/token"
"strings"
"code.google.com/p/go.tools/go/exact"
@ -102,7 +103,7 @@ func init() {
// Error has a nil package in its qualified name since it is in no package
var methods ObjSet
sig := &Signature{results: NewTuple(&Var{name: "", typ: Typ[String]})}
methods.Insert(&Func{nil, "Error", sig, nil})
methods.Insert(&Func{token.NoPos, nil, nil, "Error", sig, nil})
def(&TypeName{name: "error", typ: &Named{underlying: &Interface{methods: methods}}})
}