}
func processPackage(fset *token.FileSet, files []*ast.File) {
- _, _, err := types.Check(fset, files)
+ _, err := types.Check(fset, files)
if err != nil {
report(err)
}
// we have the scope moved from *ast.Scope to *Scope, only *Package
// will be returned.
//
-func (ctxt *Context) Check(fset *token.FileSet, files []*ast.File) (*ast.Package, *Package, error) {
+func (ctxt *Context) Check(fset *token.FileSet, files []*ast.File) (*Package, error) {
return check(ctxt, fset, files)
}
// Check is shorthand for Default.Check.
-func Check(fset *token.FileSet, files []*ast.File) (*ast.Package, *Package, error) {
+func Check(fset *token.FileSet, files []*ast.File) (*Package, error) {
return Default.Check(fset, files)
}
case _Recover:
x.mode = value
- x.typ = emptyInterface
+ x.typ = new(Interface)
case _Alignof:
x.mode = constant
files []*ast.File
// lazily initialized
- pkg *Package
- pkgscope *ast.Scope
- firsterr error
- initspec map[*ast.ValueSpec]*ast.ValueSpec // "inherited" type and initialization expressions for constant declarations
- 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
+ pkg *Package // current package
+ firsterr error // first error encountered
+ idents map[*ast.Ident]Object // maps identifiers to their unique object
+ objects map[*ast.Object]Object // maps *ast.Objects to their unique object
+ initspecs map[*ast.ValueSpec]*ast.ValueSpec // "inherited" type and initialization expressions for constant declarations
+ methods map[*TypeName]*Scope // maps type names to associated methods
+ 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
+}
+
+// lookup returns the unique Object denoted by the identifier.
+// For identifiers without assigned *ast.Object, it uses the
+// checker.idents map; for identifiers with an *ast.Object it
+// uses the checker.objects map.
+//
+// TODO(gri) Once identifier resolution is done entirely by
+// the typechecker, only the idents map is needed.
+//
+func (check *checker) lookup(ident *ast.Ident) Object {
+ astObj := ident.Obj
+ obj := check.idents[ident]
+
+ if obj != nil {
+ assert(astObj == nil || check.objects[astObj] == nil || check.objects[astObj] == obj)
+ return obj
+ }
+
+ if astObj == nil {
+ return nil
+ }
+
+ obj = check.objects[astObj]
+ if obj == nil {
+ obj = newObj(astObj)
+ check.idents[ident] = obj
+ check.objects[astObj] = obj
+ }
+
+ return obj
}
type function struct {
- obj *ast.Object // for debugging/tracing only
+ obj *Func // for debugging/tracing only
sig *Signature
body *ast.BlockStmt
}
// that need to be processed after all package-level declarations
// are typechecked.
//
-func (check *checker) later(obj *ast.Object, sig *Signature, body *ast.BlockStmt) {
+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{obj, sig, body})
+ check.funclist = append(check.funclist, function{f, sig, body})
}
}
-// declare declares an object of the given kind and name (ident) in scope;
-// decl is the corresponding declaration in the AST. An error is reported
-// if the object was declared before.
-//
-// TODO(gri) This is very similar to the declare function in go/parser; it
-// is only used to associate methods with their respective receiver base types.
-// In a future version, it might be simpler and cleaner to do all the resolution
-// in the type-checking phase. It would simplify the parser, AST, and also
-// reduce some amount of code duplication.
-//
-func (check *checker) declare(scope *ast.Scope, kind ast.ObjKind, ident *ast.Ident, decl ast.Decl) {
- assert(ident.Obj == nil) // identifier already declared or resolved
- obj := ast.NewObj(kind, ident.Name)
- obj.Decl = decl
- ident.Obj = obj
+func (check *checker) declareIdent(scope *Scope, ident *ast.Ident, obj Object) {
+ assert(check.lookup(ident) == nil) // identifier already declared or resolved
+ check.idents[ident] = obj
if ident.Name != "_" {
if alt := scope.Insert(obj); alt != nil {
prevDecl := ""
- if pos := alt.Pos(); pos.IsValid() {
+ if pos := alt.GetPos(); pos.IsValid() {
prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", check.fset.Position(pos))
}
check.errorf(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl))
}
}
-func (check *checker) valueSpec(pos token.Pos, obj *ast.Object, lhs []*ast.Ident, typ ast.Expr, rhs []ast.Expr, iota int) {
+func (check *checker) valueSpec(pos token.Pos, obj Object, lhs []*ast.Ident, spec *ast.ValueSpec, iota int) {
if len(lhs) == 0 {
check.invalidAST(pos, "missing lhs in declaration")
return
// determine type for all of lhs, if any
// (but only set it for the object we typecheck!)
- var t Type
- if typ != nil {
- t = check.typ(typ, false)
+ var typ Type
+ if spec.Type != nil {
+ typ = check.typ(spec.Type, false)
}
// len(lhs) > 0
+ rhs := spec.Values
if len(lhs) == len(rhs) {
// check only lhs and rhs corresponding to obj
var l, r ast.Expr
for i, name := range lhs {
- if name.Obj == obj {
+ if check.lookup(name) == obj {
l = lhs[i]
r = rhs[i]
break
}
}
assert(l != nil)
- obj.Type = t
+ switch obj := obj.(type) {
+ case *Const:
+ obj.Type = typ
+ case *Var:
+ obj.Type = typ
+ default:
+ unreachable()
+ }
check.assign1to1(l, r, nil, true, iota)
return
}
// there must be a type or initialization expressions
- if t == nil && len(rhs) == 0 {
+ if typ == nil && len(rhs) == 0 {
check.invalidAST(pos, "missing type or initialization expression")
- t = Typ[Invalid]
+ typ = Typ[Invalid]
}
// if we have a type, mark all of lhs
- if t != nil {
+ if typ != nil {
for _, name := range lhs {
- name.Obj.Type = t
+ switch obj := check.lookup(name).(type) {
+ case *Const:
+ obj.Type = typ
+ case *Var:
+ obj.Type = typ
+ default:
+ unreachable()
+ }
}
}
}
}
-// object typechecks an object by assigning it a type; obj.Type must be nil.
-// Callers must check obj.Type before calling object; this eliminates a call
-// for each identifier that has been typechecked already, a common scenario.
+// object typechecks an object by assigning it a type.
//
-func (check *checker) object(obj *ast.Object, cycleOk bool) {
- assert(obj.Type == nil)
-
- switch obj.Kind {
- case ast.Bad, ast.Pkg:
+func (check *checker) object(obj Object, cycleOk bool) {
+ switch obj := obj.(type) {
+ case *Package:
// nothing to do
-
- case ast.Con, ast.Var:
- // The obj.Data field for constants and variables is initialized
- // to the respective (hypothetical, for variables) iota value by
- // the parser. The object's fields can be in one of the following
- // states:
- // Type != nil => the constant value is Data
- // Type == nil => the object is not typechecked yet, and Data can be:
- // Data is int => Data is the value of iota for this declaration
- // Data == nil => the object's expression is being evaluated
- if obj.Data == nil {
- check.errorf(obj.Pos(), "illegal cycle in initialization of %s", obj.Name)
+ case *Const:
+ if obj.Type != nil {
+ return // already checked
+ }
+ // The obj.Val field for constants is initialized to its respective
+ // iota value by the parser.
+ // The object's fields can be in one of the following states:
+ // Type != nil => the constant value is Val
+ // Type == nil => the constant is not typechecked yet, and Val can be:
+ // Val is int => Val is the value of iota for this declaration
+ // Val == nil => the object's expression is being evaluated
+ if obj.Val == nil {
+ check.errorf(obj.GetPos(), "illegal cycle in initialization of %s", obj.Name)
obj.Type = Typ[Invalid]
return
}
- spec := obj.Decl.(*ast.ValueSpec)
- iota := obj.Data.(int)
- obj.Data = nil
+ spec := obj.spec
+ iota := obj.Val.(int)
+ obj.Val = nil // mark obj as "visited" for cycle detection
// determine spec for type and initialization expressions
init := spec
- if len(init.Values) == 0 && obj.Kind == ast.Con {
- init = check.initspec[spec]
+ if len(init.Values) == 0 {
+ init = check.initspecs[spec]
}
- check.valueSpec(spec.Pos(), obj, spec.Names, init.Type, init.Values, iota)
+ check.valueSpec(spec.Pos(), obj, spec.Names, init, iota)
- case ast.Typ:
- typ := &NamedType{AstObj: obj}
+ case *Var:
+ if obj.Type != nil {
+ return // already checked
+ }
+ if obj.visited {
+ check.errorf(obj.GetPos(), "illegal cycle in initialization of %s", obj.Name)
+ obj.Type = Typ[Invalid]
+ return
+ }
+ spec := obj.decl.(*ast.ValueSpec)
+ obj.visited = true
+ check.valueSpec(spec.Pos(), obj, spec.Names, spec, 0)
+
+ case *TypeName:
+ if obj.Type != nil {
+ return // already checked
+ }
+ typ := &NamedType{Obj: obj}
obj.Type = typ // "mark" object so recursion terminates
- typ.Underlying = underlying(check.typ(obj.Decl.(*ast.TypeSpec).Type, cycleOk))
+ typ.Underlying = underlying(check.typ(obj.spec.Type, cycleOk))
// typecheck associated method signatures
- if obj.Data != nil {
- scope := obj.Data.(*ast.Scope)
+ if scope := check.methods[obj]; scope != nil {
switch t := typ.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)
+ check.errorf(m.GetPos(), "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.Objects {
- recv := m.Decl.(*ast.FuncDecl).Recv.List[0].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 []*Method
- for _, obj := range scope.Objects {
- mdecl := obj.Decl.(*ast.FuncDecl)
- sig := check.typ(mdecl.Type, cycleOk).(*Signature)
- params, _ := check.collectParams(mdecl.Recv, false)
+ for _, obj := range scope.Entries {
+ m := obj.(*Func)
+ sig := check.typ(m.decl.Type, cycleOk).(*Signature)
+ params, _ := check.collectParams(m.decl.Recv, false)
sig.Recv = params[0] // the parser/assocMethod ensure there is exactly one parameter
- obj.Type = sig
- methods = append(methods, &Method{QualifiedName{nil, obj.Name}, sig})
- check.later(obj, sig, mdecl.Body)
+ m.Type = sig
+ methods = append(methods, &Method{QualifiedName{nil, m.Name}, sig})
+ check.later(m, sig, m.decl.Body)
}
typ.Methods = methods
- obj.Data = nil // don't use obj.Data later, accidentally
+ delete(check.methods, obj) // we don't need this scope anymore
}
- case ast.Fun:
- fdecl := obj.Decl.(*ast.FuncDecl)
+ case *Func:
+ if obj.Type != nil {
+ return // already checked
+ }
+ fdecl := obj.decl
// methods are typechecked when their receivers are typechecked
if fdecl.Recv == nil {
sig := check.typ(fdecl.Type, cycleOk).(*Signature)
}
default:
- panic("unreachable")
+ unreachable()
}
}
// assocInitvals associates "inherited" initialization expressions
-// with the corresponding *ast.ValueSpec in the check.initspec map
+// with the corresponding *ast.ValueSpec in the check.initspecs map
// for constant declarations without explicit initialization expressions.
//
func (check *checker) assocInitvals(decl *ast.GenDecl) {
if len(s.Values) > 0 {
last = s
} else {
- check.initspec[s] = last
+ check.initspecs[s] = last
}
}
}
if ptr, ok := typ.(*ast.StarExpr); ok {
typ = ptr.X
}
+ // determine receiver base type name
+ ident, ok := typ.(*ast.Ident)
+ if !ok {
+ // not an identifier - parser reported error already
+ return // ignore this method
+ }
// determine receiver base type object
- var obj *ast.Object
- if ident, ok := typ.(*ast.Ident); ok && ident.Obj != nil {
- obj = ident.Obj
- if obj.Kind != ast.Typ {
+ 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)
return // ignore this method
}
- // TODO(gri) determine if obj was defined in this package
- /*
- if check.notLocal(obj) {
- check.errorf(ident.Pos(), "cannot define methods on non-local type %s", ident.Name)
- return // ignore this method
- }
- */
+ if obj.spec == nil {
+ check.errorf(ident.Pos(), "cannot define method on non-local type %s", ident.Name)
+ return // ignore this method
+ }
+ tname = obj
} else {
- // If it's not an identifier or the identifier wasn't declared/resolved,
- // the parser/resolver already reported an error. Nothing to do here.
+ // identifier not declared/resolved - parser reported error already
return // ignore this method
}
// declare method in receiver base type scope
- var scope *ast.Scope
- if obj.Data != nil {
- scope = obj.Data.(*ast.Scope)
- } else {
- scope = ast.NewScope(nil)
- obj.Data = scope
- }
- check.declare(scope, ast.Fun, meth.Name, meth)
-}
-
-func (check *checker) assocInitvalsOrMethod(decl ast.Decl) {
- switch d := decl.(type) {
- case *ast.GenDecl:
- if d.Tok == token.CONST {
- check.assocInitvals(d)
- }
- case *ast.FuncDecl:
- if d.Recv != nil {
- check.assocMethod(d)
- }
+ scope := check.methods[tname]
+ if scope == nil {
+ scope = new(Scope)
+ check.methods[tname] = scope
}
+ check.declareIdent(scope, meth.Name, &Func{Name: meth.Name.Name, decl: meth})
}
func (check *checker) decl(decl ast.Decl) {
for _, spec := range d.Specs {
switch s := spec.(type) {
case *ast.ImportSpec:
- // nothing to do (handled by ast.NewPackage)
+ // nothing to do (handled by check.resolve)
case *ast.ValueSpec:
for _, name := range s.Names {
- if obj := name.Obj; obj.Type == nil {
- check.object(obj, false)
- }
+ check.object(check.lookup(name), false)
}
case *ast.TypeSpec:
- if obj := s.Name.Obj; obj.Type == nil {
- check.object(obj, false)
- }
+ check.object(check.lookup(s.Name), false)
default:
check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s)
}
if d.Recv != nil {
return
}
- obj := d.Name.Obj
+ obj := check.lookup(d.Name)
// Initialization functions don't have an object associated with them
// since they are not in any scope. Create a dummy object for them.
if d.Name.Name == "init" {
assert(obj == nil) // all other functions should have an object
- obj = ast.NewObj(ast.Fun, d.Name.Name)
- obj.Decl = d
- d.Name.Obj = obj
- }
- if obj.Type == nil {
- check.object(obj, false)
+ obj = &Func{Name: d.Name.Name, decl: d}
+ check.idents[d.Name] = obj
}
+ check.object(obj, false)
default:
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
}
}
-// iterate calls f for each package-level declaration.
-func (check *checker) iterate(f func(*checker, ast.Decl)) {
- for _, file := range check.files {
- for _, decl := range file.Decls {
- f(check, decl)
- }
- }
-}
-
// A bailout panic is raised to indicate early termination.
type bailout struct{}
-func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (astpkg *ast.Package, pkg *Package, err error) {
+func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (pkg *Package, err error) {
// initialize checker
check := checker{
- ctxt: ctxt,
- fset: fset,
- files: files,
- initspec: make(map[*ast.ValueSpec]*ast.ValueSpec),
+ ctxt: ctxt,
+ fset: fset,
+ files: files,
+ idents: make(map[*ast.Ident]Object),
+ objects: make(map[*ast.Object]Object),
+ initspecs: make(map[*ast.ValueSpec]*ast.ValueSpec),
+ methods: make(map[*TypeName]*Scope),
}
// handle panics
err = check.firsterr
default:
// unexpected panic: don't crash clients
- // panic(p) // enable for debugging
+ panic(p) // enable for debugging
// TODO(gri) add a test case for this scenario
err = fmt.Errorf("types internal error: %v", p)
}
return pkg, err
}
}
- astpkg, pkg = check.resolve(imp)
-
- // Imported packages and all types refer to types.Objects,
- // the current package files' AST uses ast.Objects.
- // Use an ast.Scope for the current package scope.
+ pkg, methods := check.resolve(imp)
check.pkg = pkg
- check.pkgscope = astpkg.Scope
- // determine missing constant initialization expressions
- // and associate methods with types
- check.iterate((*checker).assocInitvalsOrMethod)
+ // associate methods with types
+ for _, m := range methods {
+ check.assocMethod(m)
+ }
// typecheck all declarations
- check.iterate((*checker).decl)
+ for _, f := range check.files {
+ for _, d := range f.Decls {
+ check.decl(d)
+ }
+ }
// typecheck all function/method bodies
// (funclist may grow when checking statements - do not use range clause!)
// Declare builtins for testing.
// Not done in an init func to avoid an init race with
// the construction of the Universe var.
- def(ast.Fun, "assert", &builtin{aType, _Assert, "assert", 1, false, true})
- def(ast.Fun, "trace", &builtin{aType, _Trace, "trace", 0, true, true})
+ def(&Func{Name: "assert", Type: &builtin{_Assert, "assert", 1, false, true}})
+ def(&Func{Name: "trace", Type: &builtin{_Trace, "trace", 0, true, true}})
// For easy debugging w/o changing the testing code,
// if there is a local test file, only test that file.
}
}
-func unimplemented() {
- // enable for debugging
- // panic("unimplemented")
-}
-
func unreachable() {
panic("unreachable")
}
writeType(buf, t.Elt)
case *NamedType:
- var s string
- switch {
- case t.Obj != nil:
+ s := "<NamedType w/o object>"
+ if t.Obj != nil {
s = t.Obj.GetName()
- case t.AstObj != nil:
- s = t.AstObj.Name
- default:
- s = "<NamedType w/o object>"
}
buf.WriteString(s)
if list == nil {
return
}
- var last *ast.Object
+ var last *Var
for i, field := range list.List {
ftype := field.Type
if t, _ := ftype.(*ast.Ellipsis); t != nil {
if len(field.Names) > 0 {
// named parameter
for _, name := range field.Names {
- obj := name.Obj
- obj.Type = typ
- last = obj
- params = append(params, &Var{Name: obj.Name, Type: typ})
+ par := check.lookup(name).(*Var)
+ par.Type = typ
+ last = par
+ copy := *par
+ params = append(params, ©)
}
} else {
// anonymous parameter
- obj := ast.NewObj(ast.Var, "")
- obj.Type = typ
- last = obj
- params = append(params, &Var{Name: obj.Name, Type: typ})
+ par := &Var{Type: typ}
+ last = nil // not accessible inside function
+ params = append(params, par)
}
}
// For a variadic function, change the last parameter's object type
// from T to []T (this is the type used inside the function), but
// keep the params list unchanged (this is the externally visible type).
- if isVariadic {
- last.Type = &Slice{Elt: last.Type.(Type)}
+ if isVariadic && last != nil {
+ last.Type = &Slice{Elt: last.Type}
}
return
}
case *Basic:
fields = append(fields, &Field{QualifiedName{nil, t.Name}, typ, tag, true})
case *NamedType:
- var name string
- switch {
- case t.Obj != nil:
- name = t.Obj.GetName()
- case t.AstObj != nil:
- name = t.AstObj.Name
- default:
- unreachable()
- }
- fields = append(fields, &Field{QualifiedName{nil, name}, typ, tag, true})
+ fields = append(fields, &Field{QualifiedName{nil, t.Obj.GetName()}, typ, tag, true})
default:
if typ != Typ[Invalid] {
check.invalidAST(f.Type.Pos(), "anonymous field type %s must be named", typ)
// For details, see comment in go/parser/parser.go, method parseElement.
func (check *checker) compositeLitKey(key ast.Expr) {
if ident, ok := key.(*ast.Ident); ok && ident.Obj == nil {
- ident.Obj = check.pkgscope.Lookup(ident.Name)
- if ident.Obj == nil {
+ if obj := check.pkg.Scope.Lookup(ident.Name); obj != nil {
+ check.idents[ident] = obj
+ } else {
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
}
}
var z operand
z.mode = variable
z.expr = nil // TODO(gri) can we do better here? (for good error messages)
- z.typ = par.Type.(Type)
+ z.typ = par.Type
if arg != nil {
check.expr(x, arg, z.typ, -1)
check.invalidOp(e.Pos(), "cannot use _ as value or type")
goto Error
}
- obj := e.Obj
+ obj := check.lookup(e)
if obj == nil {
goto Error // error was reported before
}
- if obj.Type == nil {
- check.object(obj, cycleOk)
- }
- switch obj.Kind {
- case ast.Bad:
- goto Error // error was reported before
- case ast.Pkg:
+ check.object(obj, cycleOk)
+ switch obj := obj.(type) {
+ case *Package:
check.errorf(e.Pos(), "use of package %s not in selector", obj.Name)
goto Error
- case ast.Con:
- if obj.Data == nil {
+ case *Const:
+ if obj.Val == nil {
goto Error // cycle detected
}
x.mode = constant
}
x.val = int64(iota)
} else {
- x.val = obj.Data
+ x.val = obj.Val
}
- case ast.Typ:
+ case *TypeName:
x.mode = typexpr
- if !cycleOk && underlying(obj.Type.(Type)) == nil {
- check.errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name)
+ if !cycleOk && underlying(obj.Type) == nil {
+ 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
}
- case ast.Var:
+ case *Var:
x.mode = variable
- case ast.Fun:
+ case *Func:
x.mode = value
default:
unreachable()
}
- x.typ = obj.Type.(Type)
+ x.typ = obj.GetType()
case *ast.Ellipsis:
// ellipses are handled explicitly where they are legal
// can only appear in qualified identifiers which are mapped to
// selector expressions.
if ident, ok := e.X.(*ast.Ident); ok {
- if obj := ident.Obj; obj != nil && obj.Kind == ast.Pkg {
- exp := obj.Data.(*Package).Scope.Lookup(sel)
+ if pkg, ok := check.lookup(ident).(*Package); ok {
+ exp := pkg.Scope.Lookup(sel)
if exp == nil {
check.errorf(e.Sel.Pos(), "cannot refer to unexported %s", sel)
goto Error
for i, obj := range t.Values {
x.mode = value
x.expr = nil // TODO(gri) can we do better here? (for good error messages)
- x.typ = obj.Type.(Type)
+ x.typ = obj.Type
check.argument(sig, i, nil, x, passSlice && i+1 == n)
}
} else {
x.mode = novalue
case 1:
x.mode = value
- x.typ = sig.Results[0].Type.(Type)
+ x.typ = sig.Results[0].Type
default:
x.mode = value
x.typ = &Result{Values: sig.Results}
func (p *gcParser) parseBasicType() Type {
id := p.expect(scanner.Ident)
obj := Universe.Lookup(id)
- if obj == nil || obj.Kind != ast.Typ {
- p.errorf("not a basic type: %s", id)
+ if obj, ok := obj.(*TypeName); ok {
+ return obj.Type
}
- return obj.Type.(Type)
+ p.errorf("not a basic type: %s", id)
+ return nil
}
// ArrayType = "[" int_lit "]" Type .
package types
+import (
+ "go/ast"
+ "go/token"
+)
+
// 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 {
- anObject()
GetName() string
+ GetType() Type
+ GetPos() token.Pos
+
+ anObject()
}
// A Package represents the contents (objects) of a Go package.
type Package struct {
- implementsObject
Name string
Path string // import path, "" for current (non-imported) package
- Scope *Scope // nil for current (non-imported) package for now
- Imports map[string]*Package // map of import paths to packages
+ Scope *Scope // package-level scope
+ Imports map[string]*Package // map of import paths to imported packages
+
+ spec *ast.ImportSpec
}
// A Const represents a declared constant.
type Const struct {
- implementsObject
Name string
Type Type
Val interface{}
+
+ spec *ast.ValueSpec
}
// A TypeName represents a declared type.
type TypeName struct {
- implementsObject
Name string
Type Type // *NamedType or *Basic
+
+ spec *ast.TypeSpec
}
// A Variable represents a declared variable (including function parameters and results).
type Var struct {
- implementsObject
Name string
Type Type
+
+ visited bool // for initialization cycle detection
+ decl interface{}
}
// A Func represents a declared function.
type Func struct {
- implementsObject
Name string
Type Type // *Signature or *Builtin
+
+ decl *ast.FuncDecl
}
func (obj *Package) GetName() string { return obj.Name }
func (obj *Var) GetName() string { return obj.Name }
func (obj *Func) GetName() string { return obj.Name }
-func (obj *Package) GetType() Type { return nil }
+func (obj *Package) GetType() Type { return Typ[Invalid] }
func (obj *Const) GetType() Type { return obj.Type }
func (obj *TypeName) GetType() Type { return obj.Type }
func (obj *Var) GetType() Type { return obj.Type }
func (obj *Func) GetType() Type { return obj.Type }
-// All concrete objects embed implementsObject which
-// ensures that they all implement the Object interface.
-type implementsObject struct{}
-
-func (*implementsObject) anObject() {}
-
-// A Scope maintains the set of named language entities declared
-// in the scope and a link to the immediately surrounding (outer)
-// scope.
-//
-type Scope struct {
- Outer *Scope
- Elems []Object // scope entries in insertion order
- large map[string]Object // for fast lookup - only used for larger scopes
-}
-
-// Lookup returns the object with the given name if it is
-// found in scope s, otherwise it returns nil. Outer scopes
-// are ignored.
-//
-func (s *Scope) Lookup(name string) Object {
- if s.large != nil {
- return s.large[name]
+func (obj *Package) GetPos() token.Pos { return obj.spec.Pos() }
+func (obj *Const) GetPos() token.Pos {
+ for _, n := range obj.spec.Names {
+ if n.Name == obj.Name {
+ return n.Pos()
+ }
}
- for _, obj := range s.Elems {
- if obj.GetName() == name {
- return obj
+ return token.NoPos
+}
+func (obj *TypeName) GetPos() token.Pos { return obj.spec.Pos() }
+func (obj *Var) GetPos() token.Pos {
+ switch d := obj.decl.(type) {
+ case *ast.Field:
+ for _, n := range d.Names {
+ if n.Name == obj.Name {
+ return n.Pos()
+ }
+ }
+ case *ast.ValueSpec:
+ for _, n := range d.Names {
+ if n.Name == obj.Name {
+ return n.Pos()
+ }
+ }
+ case *ast.AssignStmt:
+ for _, x := range d.Lhs {
+ if ident, isIdent := x.(*ast.Ident); isIdent && ident.Name == obj.Name {
+ return ident.Pos()
+ }
}
}
- return nil
+ return token.NoPos
}
+func (obj *Func) GetPos() token.Pos { return obj.decl.Name.Pos() }
+
+func (*Package) anObject() {}
+func (*Const) anObject() {}
+func (*TypeName) anObject() {}
+func (*Var) anObject() {}
+func (*Func) anObject() {}
-// 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.
+// newObj returns a new Object for a given *ast.Object.
+// It does not canonicalize them (it always returns a new one).
+// For canonicalization, see check.lookup.
//
-func (s *Scope) Insert(obj Object) Object {
- name := obj.GetName()
- if alt := s.Lookup(name); alt != nil {
- return alt
- }
- s.Elems = append(s.Elems, obj)
- if len(s.Elems) > 20 {
- if s.large == nil {
- m := make(map[string]Object, len(s.Elems))
- for _, obj := range s.Elems {
- m[obj.GetName()] = obj
- }
- s.large = m
+// TODO(gri) Once we do identifier resolution completely in
+// in the typechecker, this functionality can go.
+//
+func newObj(astObj *ast.Object) Object {
+ name := astObj.Name
+ typ, _ := astObj.Type.(Type)
+ switch astObj.Kind {
+ case ast.Bad:
+ // ignore
+ case ast.Pkg:
+ unreachable()
+ case ast.Con:
+ return &Const{Name: name, Type: typ, Val: astObj.Data, spec: astObj.Decl.(*ast.ValueSpec)}
+ case ast.Typ:
+ return &TypeName{Name: name, Type: typ, spec: astObj.Decl.(*ast.TypeSpec)}
+ case ast.Var:
+ switch astObj.Decl.(type) {
+ case *ast.Field, *ast.ValueSpec, *ast.AssignStmt: // these are ok
+ default:
+ unreachable()
}
- s.large[name] = obj
+ return &Var{Name: name, Type: typ, decl: astObj.Decl}
+ case ast.Fun:
+ return &Func{Name: name, Type: typ, decl: astObj.Decl.(*ast.FuncDecl)}
+ case ast.Lbl:
+ unreachable() // for now
}
+ unreachable()
return nil
}
visited[typ] = true
// look for a matching attached method
- if typ.AstObj != nil {
- assert(typ.AstObj.Data == nil) // methods must have been moved to typ.Methods
- }
for _, m := range typ.Methods {
if name.IsSame(m.QualifiedName) {
assert(m.Type != nil)
typ = deref(typ)
if t, ok := typ.(*NamedType); ok {
- if t.AstObj != nil {
- assert(t.AstObj.Data == nil) // methods must have been moved to t.Methods
- }
for _, m := range t.Methods {
if name.IsSame(m.QualifiedName) {
assert(m.Type != nil)
// Two named types are identical if their type names originate
// in the same type declaration.
if y, ok := y.(*NamedType); ok {
- switch {
- case x.Obj != nil:
- return x.Obj == y.Obj
- case x.AstObj != nil:
- return x.AstObj == y.AstObj
- default:
- unreachable()
- }
+ return x.Obj == y.Obj
}
}
import (
"fmt"
"go/ast"
+ "go/token"
"strconv"
)
-func (check *checker) declareObj(scope, altScope *ast.Scope, obj *ast.Object) {
+func (check *checker) declareObj(scope, altScope *Scope, obj Object) {
alt := scope.Insert(obj)
if alt == nil && altScope != nil {
// see if there is a conflicting declaration in altScope
- alt = altScope.Lookup(obj.Name)
+ alt = altScope.Lookup(obj.GetName())
}
if alt != nil {
prevDecl := ""
- if pos := alt.Pos(); pos.IsValid() {
+ if pos := alt.GetPos(); pos.IsValid() {
prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", check.fset.Position(pos))
}
- check.errorf(obj.Pos(), fmt.Sprintf("%s redeclared in this block%s", obj.Name, prevDecl))
+ check.errorf(obj.GetPos(), fmt.Sprintf("%s redeclared in this block%s", obj.GetName(), prevDecl))
}
}
-func resolve(scope *ast.Scope, ident *ast.Ident) bool {
+func (check *checker) resolveIdent(scope *Scope, ident *ast.Ident) bool {
for ; scope != nil; scope = scope.Outer {
if obj := scope.Lookup(ident.Name); obj != nil {
- ident.Obj = obj
+ check.idents[ident] = obj
return true
}
}
- // handle universe scope lookups
return false
}
-// TODO(gri) eventually resolve should only return *Package.
-func (check *checker) resolve(importer Importer) (*ast.Package, *Package) {
+func (check *checker) resolve(importer Importer) (pkg *Package, methods []*ast.FuncDecl) {
// complete package scope
pkgName := ""
- pkgScope := ast.NewScope(Universe)
+ pkgScope := &Scope{Outer: Universe}
i := 0
for _, file := range check.files {
check.files[i] = file
i++
- // collect top-level file objects in package scope
- for _, obj := range file.Scope.Objects {
- check.declareObj(pkgScope, nil, obj)
+ // insert top-level file objects in package scope
+ // (the parser took care of declaration errors)
+ for _, decl := range file.Decls {
+ switch d := decl.(type) {
+ case *ast.BadDecl:
+ // ignore
+ case *ast.GenDecl:
+ if d.Tok == token.CONST {
+ check.assocInitvals(d)
+ }
+ for _, spec := range d.Specs {
+ switch s := spec.(type) {
+ case *ast.ImportSpec:
+ // handled separately below
+ case *ast.ValueSpec:
+ for _, name := range s.Names {
+ if name.Name == "_" {
+ continue
+ }
+ pkgScope.Insert(check.lookup(name))
+ }
+ case *ast.TypeSpec:
+ if s.Name.Name == "_" {
+ continue
+ }
+ pkgScope.Insert(check.lookup(s.Name))
+ default:
+ check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s)
+ }
+ }
+ case *ast.FuncDecl:
+ if d.Recv != nil {
+ // collect method
+ methods = append(methods, d)
+ continue
+ }
+ if d.Name.Name == "_" || d.Name.Name == "init" {
+ continue // blank (_) and init functions are inaccessible
+ }
+ pkgScope.Insert(check.lookup(d.Name))
+ default:
+ check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
+ }
}
}
check.files = check.files[0:i]
for _, file := range check.files {
// build file scope by processing all imports
importErrors := false
- fileScope := ast.NewScope(pkgScope)
+ fileScope := &Scope{Outer: pkgScope}
for _, spec := range file.Imports {
if importer == nil {
importErrors = true
// add import to file scope
if name == "." {
// merge imported scope with file scope
- // TODO(gri) Imported packages use Objects but the current
- // package scope is based on ast.Scope and ast.Objects
- // at the moment. Don't try to convert the imported
- // objects for now. Once we get rid of ast.Object
- // dependency, this loop can be enabled again.
- panic("cannot handle dot-import")
- /*
- for _, obj := range pkg.Scope.Elems {
- check.declareObj(fileScope, pkgScope, obj)
- }
- */
+ for _, obj := range pkg.Scope.Entries {
+ check.declareObj(fileScope, pkgScope, obj)
+ }
} else if name != "_" {
// declare imported package object in file scope
// (do not re-use pkg in the file scope but create
// a new object instead; the Decl field is different
// for different files)
- obj := ast.NewObj(ast.Pkg, name)
- obj.Decl = spec
- obj.Data = pkg
+ obj := &Package{Name: name, Scope: pkg.Scope, spec: spec}
check.declareObj(fileScope, pkgScope, obj)
}
}
}
i := 0
for _, ident := range file.Unresolved {
- if !resolve(fileScope, ident) {
+ if !check.resolveIdent(fileScope, ident) {
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
file.Unresolved[i] = ident
i++
pkgScope.Outer = Universe // reset outer scope
}
- // TODO(gri) Once we have a pkgScope of type *Scope, only return *Package.
- return &ast.Package{Name: pkgName, Scope: pkgScope}, &Package{Name: pkgName, Imports: imports}
+ return &Package{Name: pkgName, Scope: pkgScope, Imports: imports}, methods
}
import (
"fmt"
"go/ast"
- "go/parser"
+ //"go/parser"
"go/scanner"
"go/token"
"testing"
}
func TestResolveQualifiedIdents(t *testing.T) {
- // parse package files
- fset := token.NewFileSet()
- files := make([]*ast.File, len(sources))
- for i, src := range sources {
- f, err := parser.ParseFile(fset, "", src, parser.DeclarationErrors)
+ return
+ // disabled for now
+ /*
+ // parse package files
+ fset := token.NewFileSet()
+ files := make([]*ast.File, len(sources))
+ for i, src := range sources {
+ f, err := parser.ParseFile(fset, "", src, parser.DeclarationErrors)
+ if err != nil {
+ t.Fatal(err)
+ }
+ files[i] = f
+ }
+
+ // resolve package AST
+ astpkg, pkg, err := Check(fset, files)
if err != nil {
t.Fatal(err)
}
- files[i] = f
- }
- // resolve package AST
- astpkg, pkg, err := Check(fset, files)
- if err != nil {
- t.Fatal(err)
- }
-
- // check that all packages were imported
- for _, name := range pkgnames {
- if pkg.Imports[name] == nil {
- t.Errorf("package %s not imported", name)
+ // check that all packages were imported
+ for _, name := range pkgnames {
+ if pkg.Imports[name] == nil {
+ t.Errorf("package %s not imported", name)
+ }
}
- }
- // TODO(gri) fix this
- // unresolved identifiers are not collected at the moment
- // check that there are no top-level unresolved identifiers
- for _, f := range astpkg.Files {
- for _, x := range f.Unresolved {
- t.Errorf("%s: unresolved global identifier %s", fset.Position(x.Pos()), x.Name)
+ // TODO(gri) fix this
+ // unresolved identifiers are not collected at the moment
+ // check that there are no top-level unresolved identifiers
+ for _, f := range astpkg.Files {
+ for _, x := range f.Unresolved {
+ t.Errorf("%s: unresolved global identifier %s", fset.Position(x.Pos()), x.Name)
+ }
}
- }
- // resolve qualified identifiers
- if err := ResolveQualifiedIdents(fset, astpkg); err != nil {
- t.Error(err)
- }
+ // resolve qualified identifiers
+ if err := ResolveQualifiedIdents(fset, astpkg); err != nil {
+ t.Error(err)
+ }
- // check that qualified identifiers are resolved
- ast.Inspect(astpkg, func(n ast.Node) bool {
- if s, ok := n.(*ast.SelectorExpr); ok {
- if x, ok := s.X.(*ast.Ident); ok {
- if x.Obj == nil {
- t.Errorf("%s: unresolved qualified identifier %s", fset.Position(x.Pos()), x.Name)
- return false
- }
- if x.Obj.Kind == ast.Pkg && s.Sel != nil && s.Sel.Obj == nil {
- t.Errorf("%s: unresolved selector %s", fset.Position(s.Sel.Pos()), s.Sel.Name)
+ // check that qualified identifiers are resolved
+ ast.Inspect(astpkg, func(n ast.Node) bool {
+ if s, ok := n.(*ast.SelectorExpr); ok {
+ if x, ok := s.X.(*ast.Ident); ok {
+ if x.Obj == nil {
+ t.Errorf("%s: unresolved qualified identifier %s", fset.Position(x.Pos()), x.Name)
+ return false
+ }
+ if x.Obj.Kind == ast.Pkg && s.Sel != nil && s.Sel.Obj == nil {
+ t.Errorf("%s: unresolved selector %s", fset.Position(s.Sel.Pos()), s.Sel.Name)
+ return false
+ }
return false
}
return false
}
- return false
- }
- return true
- })
+ return true
+ })
+ */
}
--- /dev/null
+// 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
+
+// A Scope maintains the set of named language entities declared
+// in the scope and a link to the immediately surrounding (outer)
+// scope.
+//
+type Scope struct {
+ Outer *Scope
+ Entries []Object // scope entries in insertion order
+ large map[string]Object // for fast lookup - only used for larger scopes
+}
+
+// Lookup returns the object with the given name if it is
+// found in scope s, otherwise it returns nil. Outer scopes
+// are ignored.
+//
+func (s *Scope) Lookup(name string) Object {
+ if s.large != nil {
+ return s.large[name]
+ }
+ for _, obj := range s.Entries {
+ if obj.GetName() == name {
+ return obj
+ }
+ }
+ return nil
+}
+
+// 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.
+//
+func (s *Scope) Insert(obj Object) Object {
+ name := obj.GetName()
+ if alt := s.Lookup(name); alt != nil {
+ return alt
+ }
+ s.Entries = append(s.Entries, obj)
+
+ // If the scope size reaches a threshold, use a map for faster lookups.
+ const threshold = 20
+ if len(s.Entries) > threshold {
+ if s.large == nil {
+ m := make(map[string]Object, len(s.Entries))
+ for _, obj := range s.Entries {
+ m[obj.GetName()] = obj
+ }
+ s.large = m
+ }
+ s.large[name] = obj
+ }
+
+ return nil
+}
}
// lhs may or may not be typed yet
- obj := ident.Obj
+ obj := check.lookup(ident)
var typ Type
- if obj.Type != nil {
- typ = obj.Type.(Type)
+ if t := obj.GetType(); t != nil {
+ typ = t
}
if rhs != nil {
typ = Typ[Invalid]
if x.mode != invalid {
typ = x.typ
- if obj.Kind == ast.Var && isUntyped(typ) {
+ if _, ok := obj.(*Var); ok && isUntyped(typ) {
if x.isNil() {
check.errorf(x.pos(), "use of untyped nil")
x.mode = invalid
}
}
}
- obj.Type = typ
+ switch obj := obj.(type) {
+ case *Const:
+ obj.Type = typ
+ case *Var:
+ obj.Type = typ
+ default:
+ unreachable()
+ }
}
if x.mode != invalid {
var z operand
- switch obj.Kind {
- case ast.Con:
+ switch obj.(type) {
+ case *Const:
z.mode = constant
- case ast.Var:
+ case *Var:
z.mode = variable
default:
unreachable()
}
// for constants, set their value
- if obj.Kind == ast.Con {
- assert(obj.Data == nil)
+ if obj, ok := obj.(*Const); ok {
+ assert(obj.Val == nil)
if x.mode != invalid {
if x.mode == constant {
if isConstType(x.typ) {
- obj.Data = x.val
+ obj.Val = x.val
} else {
check.errorf(x.pos(), "%s has invalid constant type", x)
}
check.errorf(x.pos(), "%s is not constant", x)
}
}
- if obj.Data == nil {
+ if obj.Val == nil {
// set the constant to its type's zero value to reduce spurious errors
- switch typ := underlying(obj.Type.(Type)); {
+ switch typ := underlying(obj.Type); {
case typ == Typ[Invalid]:
// ignore
case isBoolean(typ):
- obj.Data = false
+ obj.Val = false
case isNumeric(typ):
- obj.Data = int64(0)
+ obj.Val = int64(0)
case isString(typ):
- obj.Data = ""
+ obj.Val = ""
case hasNil(typ):
- obj.Data = nilConst
+ obj.Val = nilConst
default:
// in all other cases just prevent use of the constant
- obj.Kind = ast.Bad
+ // TODO(gri) re-evaluate this code
+ obj.Val = nilConst
}
}
}
// assignNtoM typechecks a general assignment. If decl is set, the lhs operands
// must be identifiers. If their types are not set, they are deduced from the
// types of the corresponding rhs expressions. iota >= 0 indicates that the
-// "assignment" is part of a constant declaration.
+// "assignment" is part of a constant/variable declaration.
// Precondition: len(lhs) > 0 .
//
func (check *checker) assignNtoM(lhs, rhs []ast.Expr, decl bool, iota int) {
x.mode = value
for i, obj := range t.Values {
x.expr = nil // TODO(gri) should do better here
- x.typ = obj.Type.(Type)
+ x.typ = obj.Type
check.assign1to1(lhs[i], nil, &x, decl, iota)
}
return
if iota >= 0 {
// declaration
for _, e := range lhs {
- if ident, ok := e.(*ast.Ident); ok {
- ident.Obj.Type = Typ[Invalid]
+ if name, ok := e.(*ast.Ident); ok {
+ switch obj := check.lookup(name).(type) {
+ case *Const:
+ obj.Type = Typ[Invalid]
+ case *Var:
+ obj.Type = Typ[Invalid]
+ default:
+ unreachable()
+ }
}
}
}
named := false // if set, function has named results
for i, res := range sig.Results {
if len(res.Name) > 0 {
- // a blank (_) result parameter is a named result parameter!
+ // a blank (_) result parameter is a named result
named = true
}
name := ast.NewIdent(res.Name)
name.NamePos = s.Pos()
- // TODO(gri) Avoid creating new objects here once we
- // move away from ast.Objects completely.
- obj := ast.NewObj(ast.Var, res.Name)
- obj.Type = res.Type
- name.Obj = obj
+ check.idents[name] = &Var{Name: res.Name, Type: res.Type}
lhs[i] = name
}
if len(s.Results) > 0 || !named {
}
case *ast.BranchStmt:
- unimplemented()
+ // TODO(gri) implement this
case *ast.BlockStmt:
check.stmtList(s.List)
tag := s.Tag
if tag == nil {
// use fake true tag value and position it at the opening { of the switch
- tag = &ast.Ident{NamePos: s.Body.Lbrace, Name: "true", Obj: Universe.Lookup("true")}
+ ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"}
+ check.idents[ident] = Universe.Lookup("true")
+ tag = ident
}
check.expr(&x, tag, nil, -1)
// remaining syntactic errors are considered AST errors here.
// TODO(gri) better factoring of error handling (invalid ASTs)
//
- var lhs *ast.Object // lhs identifier object or nil
+ var lhs *Var // lhs variable or nil
var rhs ast.Expr
switch guard := s.Assign.(type) {
case *ast.ExprStmt:
check.invalidAST(s.Pos(), "incorrect form of type switch guard")
return
}
- lhs = ident.Obj
+ lhs = check.lookup(ident).(*Var)
rhs = guard.Rhs[0]
default:
check.invalidAST(s.Pos(), "incorrect form of type switch guard")
func (T5 /* ERROR "invalid receiver" */) m2() {}
// Methods associated with non-local or unnamed types.
-// func (int) m() {} TODO(gri) check for methods associated with external (not package-local) 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() {}
// A Basic represents a basic type.
type Basic struct {
- implementsType
Kind BasicKind
Info BasicInfo
Size int64
// An Array represents an array type [Len]Elt.
type Array struct {
- implementsType
Len int64
Elt Type
}
// A Slice represents a slice type []Elt.
type Slice struct {
- implementsType
Elt Type
}
// A Struct represents a struct type struct{...}.
type Struct struct {
- implementsType
Fields []*Field
}
// A Pointer represents a pointer type *Base.
type Pointer struct {
- implementsType
Base Type
}
// A Result represents a (multi-value) function call result.
type Result struct {
- implementsType
Values []*Var // Signature.Results of the function called
}
// A Signature represents a user-defined function type func(...) (...).
type Signature struct {
- implementsType
Recv *Var // nil if not a method
Params []*Var // (incoming) parameters from left to right; or nil
Results []*Var // (outgoing) results from left to right; or nil
// A builtin represents the type of a built-in function.
type builtin struct {
- implementsType
id builtinId
name string
nargs int // number of arguments (minimum if variadic)
// An Interface represents an interface type interface{...}.
type Interface struct {
- implementsType
Methods []*Method // TODO(gri) consider keeping them in sorted order
}
// A Map represents a map type map[Key]Elt.
type Map struct {
- implementsType
Key, Elt Type
}
// A Chan represents a channel type chan Elt, <-chan Elt, or chan<-Elt.
type Chan struct {
- implementsType
Dir ast.ChanDir
Elt Type
}
// A NamedType represents a named type as declared in a type declaration.
type NamedType struct {
- implementsType
- // TODO(gri) remove AstObj once we have moved away from ast.Objects
- Obj Object // corresponding declared object (imported package)
- AstObj *ast.Object // corresponding declared object (current package)
- Underlying Type // nil if not fully declared yet; never a *NamedType
- Methods []*Method // TODO(gri) consider keeping them in sorted order
-}
-
-// All concrete types embed implementsType which
-// ensures that all types implement the Type interface.
-type implementsType struct{}
-
-func (*implementsType) aType() {}
+ Obj *TypeName // corresponding declared object
+ Underlying Type // nil if not fully declared yet; never a *NamedType
+ Methods []*Method // TODO(gri) consider keeping them in sorted order
+}
+
+func (*Basic) aType() {}
+func (*Array) aType() {}
+func (*Slice) aType() {}
+func (*Struct) aType() {}
+func (*Pointer) aType() {}
+func (*Result) aType() {}
+func (*Signature) aType() {}
+func (*builtin) aType() {}
+func (*Interface) aType() {}
+func (*Map) aType() {}
+func (*Chan) aType() {}
+func (*NamedType) aType() {}
const filename = "<src>"
-func makePkg(t *testing.T, src string) (*ast.Package, error) {
+func makePkg(t *testing.T, src string) (*Package, error) {
file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
if err != nil {
return nil, err
}
- astpkg, _, err := Check(fset, []*ast.File{file})
- return astpkg, err
+ pkg, err := Check(fset, []*ast.File{file})
+ return pkg, err
}
type testEntry struct {
t.Errorf("%s: %s", src, err)
continue
}
- typ := underlying(pkg.Scope.Lookup("T").Type.(Type))
+ typ := underlying(pkg.Scope.Lookup("T").GetType())
str := typeString(typ)
if str != test.str {
t.Errorf("%s: got %s, want %s", test.src, str, test.str)
)
var (
- aType implementsType
- Universe *ast.Scope
- Unsafe *Package // package unsafe
+ Universe *Scope
+ Unsafe *Package
+ universeIota *Const
)
// Predeclared types, indexed by BasicKind.
var Typ = [...]*Basic{
- Invalid: {aType, Invalid, 0, 0, "invalid type"},
-
- Bool: {aType, Bool, IsBoolean, 1, "bool"},
- Int: {aType, Int, IsInteger, 0, "int"},
- Int8: {aType, Int8, IsInteger, 1, "int8"},
- Int16: {aType, Int16, IsInteger, 2, "int16"},
- Int32: {aType, Int32, IsInteger, 4, "int32"},
- Int64: {aType, Int64, IsInteger, 8, "int64"},
- Uint: {aType, Uint, IsInteger | IsUnsigned, 0, "uint"},
- Uint8: {aType, Uint8, IsInteger | IsUnsigned, 1, "uint8"},
- Uint16: {aType, Uint16, IsInteger | IsUnsigned, 2, "uint16"},
- Uint32: {aType, Uint32, IsInteger | IsUnsigned, 4, "uint32"},
- Uint64: {aType, Uint64, IsInteger | IsUnsigned, 8, "uint64"},
- Uintptr: {aType, Uintptr, IsInteger | IsUnsigned, 0, "uintptr"},
- Float32: {aType, Float32, IsFloat, 4, "float32"},
- Float64: {aType, Float64, IsFloat, 8, "float64"},
- Complex64: {aType, Complex64, IsComplex, 8, "complex64"},
- Complex128: {aType, Complex128, IsComplex, 16, "complex128"},
- String: {aType, String, IsString, 0, "string"},
- UnsafePointer: {aType, UnsafePointer, 0, 0, "Pointer"},
-
- UntypedBool: {aType, UntypedBool, IsBoolean | IsUntyped, 0, "untyped boolean"},
- UntypedInt: {aType, UntypedInt, IsInteger | IsUntyped, 0, "untyped integer"},
- UntypedRune: {aType, UntypedRune, IsInteger | IsUntyped, 0, "untyped rune"},
- UntypedFloat: {aType, UntypedFloat, IsFloat | IsUntyped, 0, "untyped float"},
- UntypedComplex: {aType, UntypedComplex, IsComplex | IsUntyped, 0, "untyped complex"},
- UntypedString: {aType, UntypedString, IsString | IsUntyped, 0, "untyped string"},
- UntypedNil: {aType, UntypedNil, IsUntyped, 0, "untyped nil"},
+ Invalid: {Invalid, 0, 0, "invalid type"},
+
+ Bool: {Bool, IsBoolean, 1, "bool"},
+ Int: {Int, IsInteger, 0, "int"},
+ Int8: {Int8, IsInteger, 1, "int8"},
+ Int16: {Int16, IsInteger, 2, "int16"},
+ Int32: {Int32, IsInteger, 4, "int32"},
+ Int64: {Int64, IsInteger, 8, "int64"},
+ Uint: {Uint, IsInteger | IsUnsigned, 0, "uint"},
+ Uint8: {Uint8, IsInteger | IsUnsigned, 1, "uint8"},
+ Uint16: {Uint16, IsInteger | IsUnsigned, 2, "uint16"},
+ Uint32: {Uint32, IsInteger | IsUnsigned, 4, "uint32"},
+ Uint64: {Uint64, IsInteger | IsUnsigned, 8, "uint64"},
+ Uintptr: {Uintptr, IsInteger | IsUnsigned, 0, "uintptr"},
+ Float32: {Float32, IsFloat, 4, "float32"},
+ Float64: {Float64, IsFloat, 8, "float64"},
+ Complex64: {Complex64, IsComplex, 8, "complex64"},
+ Complex128: {Complex128, IsComplex, 16, "complex128"},
+ String: {String, IsString, 0, "string"},
+ UnsafePointer: {UnsafePointer, 0, 0, "Pointer"},
+
+ UntypedBool: {UntypedBool, IsBoolean | IsUntyped, 0, "untyped boolean"},
+ UntypedInt: {UntypedInt, IsInteger | IsUntyped, 0, "untyped integer"},
+ UntypedRune: {UntypedRune, IsInteger | IsUntyped, 0, "untyped rune"},
+ UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, 0, "untyped float"},
+ UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, 0, "untyped complex"},
+ UntypedString: {UntypedString, IsString | IsUntyped, 0, "untyped string"},
+ UntypedNil: {UntypedNil, IsUntyped, 0, "untyped nil"},
}
var aliases = [...]*Basic{
- {aType, Byte, IsInteger | IsUnsigned, 1, "byte"},
- {aType, Rune, IsInteger, 4, "rune"},
+ {Byte, IsInteger | IsUnsigned, 1, "byte"},
+ {Rune, IsInteger, 4, "rune"},
}
-var predeclaredConstants = [...]*struct {
- kind BasicKind
- name string
- val interface{}
-}{
- {UntypedBool, "true", true},
- {UntypedBool, "false", false},
- {UntypedInt, "iota", zeroConst},
- {UntypedNil, "nil", nilConst},
+var predeclaredConstants = [...]*Const{
+ {"true", Typ[UntypedBool], true, nil},
+ {"false", Typ[UntypedBool], false, nil},
+ {"iota", Typ[UntypedInt], zeroConst, nil},
+ {"nil", Typ[UntypedNil], nilConst, nil},
}
var predeclaredFunctions = [...]*builtin{
- {aType, _Append, "append", 1, true, false},
- {aType, _Cap, "cap", 1, false, false},
- {aType, _Close, "close", 1, false, true},
- {aType, _Complex, "complex", 2, false, false},
- {aType, _Copy, "copy", 2, false, true},
- {aType, _Delete, "delete", 2, false, true},
- {aType, _Imag, "imag", 1, false, false},
- {aType, _Len, "len", 1, false, false},
- {aType, _Make, "make", 1, true, false},
- {aType, _New, "new", 1, false, false},
- {aType, _Panic, "panic", 1, false, true},
- {aType, _Print, "print", 1, true, true},
- {aType, _Println, "println", 1, true, true},
- {aType, _Real, "real", 1, false, false},
- {aType, _Recover, "recover", 0, false, true},
-
- {aType, _Alignof, "Alignof", 1, false, false},
- {aType, _Offsetof, "Offsetof", 1, false, false},
- {aType, _Sizeof, "Sizeof", 1, false, false},
+ {_Append, "append", 1, true, false},
+ {_Cap, "cap", 1, false, false},
+ {_Close, "close", 1, false, true},
+ {_Complex, "complex", 2, false, false},
+ {_Copy, "copy", 2, false, true},
+ {_Delete, "delete", 2, false, true},
+ {_Imag, "imag", 1, false, false},
+ {_Len, "len", 1, false, false},
+ {_Make, "make", 1, true, false},
+ {_New, "new", 1, false, false},
+ {_Panic, "panic", 1, false, true},
+ {_Print, "print", 1, true, true},
+ {_Println, "println", 1, true, true},
+ {_Real, "real", 1, false, false},
+ {_Recover, "recover", 0, false, true},
+
+ {_Alignof, "Alignof", 1, false, false},
+ {_Offsetof, "Offsetof", 1, false, false},
+ {_Sizeof, "Sizeof", 1, false, false},
}
-// commonly used types
-var (
- emptyInterface = new(Interface)
-)
-
-// commonly used constants
-var (
- universeIota *ast.Object
-)
-
func init() {
- // Universe scope
- Universe = ast.NewScope(nil)
-
- // unsafe package and its scope
+ Universe = new(Scope)
Unsafe = &Package{Name: "unsafe", Scope: new(Scope)}
// predeclared types
for _, t := range Typ {
- def(ast.Typ, t.Name, t)
+ def(&TypeName{Name: t.Name, Type: t})
}
for _, t := range aliases {
- def(ast.Typ, t.Name, t)
+ def(&TypeName{Name: t.Name, Type: t})
}
// error type
{
err := &Method{QualifiedName{Name: "Error"}, &Signature{Results: []*Var{{Name: "", Type: Typ[String]}}}}
- def(ast.Typ, "error", &NamedType{Underlying: &Interface{Methods: []*Method{err}}})
+ def(&TypeName{Name: "error", Type: &NamedType{Underlying: &Interface{Methods: []*Method{err}}}})
}
- // predeclared constants
- for _, t := range predeclaredConstants {
- obj := def(ast.Con, t.name, Typ[t.kind])
- obj.Data = t.val
+ for _, c := range predeclaredConstants {
+ def(c)
}
- // predeclared functions
for _, f := range predeclaredFunctions {
- def(ast.Fun, f.name, f)
+ def(&Func{Name: f.name, Type: f})
}
- universeIota = Universe.Lookup("iota")
+ universeIota = Universe.Lookup("iota").(*Const)
}
// Objects with names containing blanks are internal and not entered into
// a scope. Objects with exported names are inserted in the unsafe package
// scope; other objects are inserted in the universe scope.
//
-func def(kind ast.ObjKind, name string, typ Type) *ast.Object {
- // insert non-internal objects into respective scope
- if strings.Index(name, " ") < 0 {
- // exported identifiers go into package unsafe
- if ast.IsExported(name) {
- var obj Object
- switch kind {
- case ast.Typ:
- obj = &TypeName{Name: name, Type: typ}
- case ast.Fun:
- obj = &Func{Name: name, Type: typ}
- default:
- unreachable()
-
- }
- if Unsafe.Scope.Insert(obj) != nil {
- panic("internal error: double declaration")
- }
- } else {
- obj := ast.NewObj(kind, name)
- obj.Decl = Universe
- obj.Type = typ
- if typ, ok := typ.(*NamedType); ok {
- typ.AstObj = obj
- }
- if Universe.Insert(obj) != nil {
- panic("internal error: double declaration")
- }
- return obj
- }
+func def(obj Object) {
+ name := obj.GetName()
+ if strings.Index(name, " ") >= 0 {
+ return // nothing to do
+ }
+ // fix Obj link for named types
+ if typ, ok := obj.GetType().(*NamedType); ok {
+ typ.Obj = obj.(*TypeName)
+ }
+ // exported identifiers go into package unsafe
+ scope := Universe
+ if ast.IsExported(name) {
+ scope = Unsafe.Scope
+ }
+ if scope.Insert(obj) != nil {
+ panic("internal error: double declaration")
}
- return nil
}