package main
import (
- "errors"
"flag"
"fmt"
"go/ast"
return file
}
-func parseStdin(fset *token.FileSet) (files map[string]*ast.File) {
- files = make(map[string]*ast.File)
+func parseStdin(fset *token.FileSet) (files []*ast.File) {
src, err := ioutil.ReadAll(os.Stdin)
if err != nil {
report(err)
}
const filename = "<standard input>"
if file := parse(fset, filename, src); file != nil {
- files[filename] = file
+ files = []*ast.File{file}
}
return
}
-func parseFiles(fset *token.FileSet, filenames []string) (files map[string]*ast.File) {
- files = make(map[string]*ast.File)
+func parseFiles(fset *token.FileSet, filenames []string) (files []*ast.File) {
for _, filename := range filenames {
src, err := ioutil.ReadFile(filename)
if err != nil {
continue
}
if file := parse(fset, filename, src); file != nil {
- if files[filename] != nil {
- report(errors.New(fmt.Sprintf("%q: duplicate file", filename)))
- continue
- }
- files[filename] = file
+ files = append(files, file)
}
}
return
processPackage(fset, parseFiles(fset, filenames[0:i]))
}
-func processPackage(fset *token.FileSet, files map[string]*ast.File) {
- _, err := types.Check(fset, files)
+func processPackage(fset *token.FileSet, files []*ast.File) {
+ _, _, err := types.Check(fset, files)
if err != nil {
report(err)
}
// ----------------------------------------------------------------------------
// Objects
-// TODO(gri) Consider replacing the Object struct with an interface
-// and a corresponding set of object implementations.
-
// An Object describes a named language entity such as a package,
// constant, type, variable, function (incl. methods), or label.
//
// The Data fields contains object-specific data:
//
-// Kind Data type Data value
-// Pkg *Scope package scope
-// Con int iota for the respective declaration
-// Con != nil constant value
-// Typ *Scope method scope; nil if no methods
+// Kind Data type Data value
+// Pkg *types.Package package scope
+// Con int iota for the respective declaration
+// Con != nil constant value
+// Typ *Scope (used as method scope during type checking - transient)
//
type Object struct {
Kind ObjKind
Expr func(x ast.Expr, typ Type, val interface{})
// If Import is not nil, it is used instead of GcImport.
- Import ast.Importer
+ Import Importer
}
+// An Importer resolves import paths to Package objects.
+// The imports map records the packages already imported,
+// indexed by package id (canonical import path).
+// An Importer must determine the canonical import path and
+// check the map to see if it is already present in the imports map.
+// If so, the Importer can return the map entry. Otherwise, the
+// Importer should load the package data for the given path into
+// a new *Package, record pkg in the imports map, and then
+// return pkg.
+type Importer func(imports map[string]*Package, path string) (pkg *Package, err error)
+
// Default is the default context for type checking.
var Default = Context{
// TODO(gri) Perhaps this should depend on GOARCH?
// it returns the first error. If the context's Error handler is nil,
// Check terminates as soon as the first error is encountered.
//
-func (ctxt *Context) Check(fset *token.FileSet, files map[string]*ast.File) (*ast.Package, error) {
+// CAUTION: At the moment, the returned *ast.Package only contains the package
+// name and scope - the other fields are not set up. The returned
+// *Package contains the name and imports (but no scope yet). Once
+// 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) {
return check(ctxt, fset, files)
}
// Check is shorthand for Default.Check.
-func Check(fset *token.FileSet, files map[string]*ast.File) (*ast.Package, error) {
+func Check(fset *token.FileSet, files []*ast.File) (*ast.Package, *Package, error) {
return Default.Check(fset, files)
}
import (
"fmt"
"go/ast"
- "go/scanner"
"go/token"
- "sort"
)
// enable for debugging
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
check.valueSpec(spec.Pos(), obj, spec.Names, init.Type, init.Values, iota)
case ast.Typ:
- typ := &NamedType{Obj: obj}
+ typ := &NamedType{obj: obj}
obj.Type = typ // "mark" object so recursion terminates
typ.Underlying = underlying(check.typ(obj.Decl.(*ast.TypeSpec).Type, cycleOk))
// typecheck associated method signatures
}
}
// 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)
sig.Recv = params[0] // the parser/assocMethod ensure there is exactly one parameter
obj.Type = sig
+ methods = append(methods, &Method{QualifiedName{check.pkg, obj.Name}, sig})
check.later(obj, sig, mdecl.Body)
}
+ typ.Methods = methods
+ obj.Data = nil // don't use obj.Data later, accidentally
}
case ast.Fun:
}
}
-// sortedFiles returns the sorted list of package files given a package file map.
-func sortedFiles(m map[string]*ast.File) []*ast.File {
- keys := make([]string, len(m))
- i := 0
- for k, _ := range m {
- keys[i] = k
- i++
- }
- sort.Strings(keys)
-
- files := make([]*ast.File, len(m))
- for i, k := range keys {
- files[i] = m[k]
- }
-
- return files
-}
-
// A bailout panic is raised to indicate early termination.
type bailout struct{}
-func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg *ast.Package, err error) {
+func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (astpkg *ast.Package, pkg *Package, err error) {
// initialize checker
check := checker{
ctxt: ctxt,
fset: fset,
- files: sortedFiles(files),
+ files: files,
initspec: make(map[*ast.ValueSpec]*ast.ValueSpec),
}
imp := ctxt.Import
if imp == nil {
// wrap GcImport to import packages only once by default.
+ // TODO(gri) move this into resolve
imported := make(map[string]bool)
- imp = func(imports map[string]*ast.Object, path string) (*ast.Object, error) {
+ imp = func(imports map[string]*Package, path string) (*Package, error) {
if imported[path] && imports[path] != nil {
return imports[path], nil
}
return pkg, err
}
}
- pkg, err = ast.NewPackage(fset, files, imp, Universe)
- if err != nil {
- if list, _ := err.(scanner.ErrorList); len(list) > 0 {
- for _, err := range list {
- check.err(err)
- }
- } else {
- check.err(err)
- }
- }
- check.pkgscope = pkg.Scope
+ 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.
+ check.pkg = pkg
+ check.pkgscope = astpkg.Scope
// determine missing constant initialization expressions
// and associate methods with types
return
}
-func parseFiles(t *testing.T, testname string, filenames []string) (map[string]*ast.File, []error) {
- files := make(map[string]*ast.File)
+func parseFiles(t *testing.T, testname string, filenames []string) ([]*ast.File, []error) {
+ var files []*ast.File
var errlist []error
for _, filename := range filenames {
- if _, exists := files[filename]; exists {
- t.Fatalf("%s: duplicate file %s", testname, filename)
- }
file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors)
if file == nil {
t.Fatalf("%s: could not parse file %s", testname, filename)
}
- files[filename] = file
+ files = append(files, file)
if err != nil {
if list, _ := err.(scanner.ErrorList); len(list) > 0 {
for _, err := range list {
// errMap collects the regular expressions of ERROR comments found
// in files and returns them as a map of error positions to error messages.
//
-func errMap(t *testing.T, testname string, files map[string]*ast.File) map[string][]string {
+func errMap(t *testing.T, testname string, files []*ast.File) map[string][]string {
errmap := make(map[string][]string)
- for filename := range files {
+ for _, file := range files {
+ filename := fset.Position(file.Package).Filename
src, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatalf("%s: could not read %s", testname, filename)
// 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").Type = &builtin{aType, _Assert, "assert", 1, false, true}
- def(ast.Fun, "trace").Type = &builtin{aType, _Trace, "trace", 0, true, true}
+ def(ast.Fun, "assert", &builtin{aType, _Assert, "assert", 1, false, true})
+ def(ast.Fun, "trace", &builtin{aType, _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.
}
// TODO(gri) fix this - implement all checks and constant evaluation
- if x.mode != constant {
+ if x.mode != constant || !isConstType(typ) {
x.mode = value
}
x.expr = conv
writeType(buf, t.Elt)
case *NamedType:
- buf.WriteString(t.Obj.Name)
+ var s string
+ switch {
+ case t.obj != nil:
+ s = t.obj.Name
+ case t.Obj != nil:
+ s = t.Obj.GetName()
+ default:
+ s = "<NamedType w/o object>"
+ }
+ buf.WriteString(s)
default:
fmt.Fprintf(buf, "<type %T>", t)
obj := name.Obj
obj.Type = typ
last = obj
- params = append(params, &Var{obj.Name, typ})
+ params = append(params, &Var{Name: obj.Name, Type: typ})
}
} else {
// anonymous parameter
obj := ast.NewObj(ast.Var, "")
obj.Type = typ
last = obj
- params = append(params, &Var{obj.Name, typ})
+ params = append(params, &Var{Name: obj.Name, Type: typ})
}
}
// For a variadic function, change the last parameter's object type
continue
}
for _, name := range f.Names {
- methods = append(methods, &Method{name.Name, sig})
+ methods = append(methods, &Method{QualifiedName{check.pkg, name.Name}, sig})
}
} else {
// embedded interface
if len(f.Names) > 0 {
// named fields
for _, name := range f.Names {
- fields = append(fields, &Field{name.Name, typ, tag, false})
+ fields = append(fields, &Field{QualifiedName{check.pkg, name.Name}, typ, tag, false})
}
} else {
// anonymous field
switch t := deref(typ).(type) {
case *Basic:
- fields = append(fields, &Field{t.Name, typ, tag, true})
+ fields = append(fields, &Field{QualifiedName{check.pkg, t.Name}, typ, tag, true})
case *NamedType:
- fields = append(fields, &Field{t.Obj.Name, typ, tag, true})
+ var name string
+ switch {
+ case t.obj != nil:
+ name = t.obj.Name
+ case t.Obj != nil:
+ name = t.Obj.GetName()
+ default:
+ unreachable()
+ }
+ fields = append(fields, &Field{QualifiedName{check.pkg, name}, typ, tag, true})
default:
if typ != Typ[Invalid] {
check.invalidAST(f.Type.Pos(), "anonymous field type %s must be named", typ)
case token.AND:
// spec: "As an exception to the addressability
// requirement x may also be a composite literal."
- // (The spec doesn't specify whether the literal
- // can be parenthesized or not, but all compilers
- // accept parenthesized literals.)
if _, ok := unparen(x.expr).(*ast.CompositeLit); ok {
x.mode = variable
}
// selector expressions.
if ident, ok := e.X.(*ast.Ident); ok {
if obj := ident.Obj; obj != nil && obj.Kind == ast.Pkg {
- exp := obj.Data.(*ast.Scope).Lookup(sel)
+ exp := obj.Data.(*Package).Scope.Lookup(sel)
if exp == nil {
check.errorf(e.Sel.Pos(), "cannot refer to unexported %s", sel)
goto Error
}
- // simplified version of the code for *ast.Idents:
- // imported objects are always fully initialized
- switch exp.Kind {
- case ast.Con:
- assert(exp.Data != nil)
+ // Simplified version of the code for *ast.Idents:
+ // - imported packages use types.Scope and types.Objects
+ // - imported objects are always fully initialized
+ switch exp := exp.(type) {
+ case *Const:
+ assert(exp.Val != nil)
x.mode = constant
- x.val = exp.Data
- case ast.Typ:
+ x.typ = exp.Type
+ x.val = exp.Val
+ case *TypeName:
x.mode = typexpr
- case ast.Var:
+ x.typ = exp.Type
+ case *Var:
x.mode = variable
- case ast.Fun:
+ x.typ = exp.Type
+ case *Func:
x.mode = value
+ x.typ = exp.Type
default:
unreachable()
}
x.expr = e
- x.typ = exp.Type.(Type)
return
}
}
if x.mode == invalid {
goto Error
}
- mode, typ := lookupField(x.typ, sel)
+ mode, typ := lookupField(x.typ, QualifiedName{check.pkg, sel})
if mode == invalid {
check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel)
goto Error
// pointer vs non-pointer receivers => typechecker is too lenient
x.mode = value
x.typ = &Signature{
- Params: append([]*Var{{"", x.typ}}, sig.Params...),
+ Params: append([]*Var{{Type: x.typ}}, sig.Params...),
Results: sig.Results,
IsVariadic: sig.IsVariadic,
}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// This file implements an ast.Importer for gc-generated object files.
-// TODO(gri) Eventually move this into a separate package outside types.
+// This file implements an Importer for gc-generated object files.
package types
// be the beginning of the export data section. The filename is only used
// in error messages.
//
-func GcImportData(imports map[string]*ast.Object, filename, id string, data *bufio.Reader) (pkg *ast.Object, err error) {
+func GcImportData(imports map[string]*Package, filename, id string, data *bufio.Reader) (pkg *Package, err error) {
// support for gcParser error handling
defer func() {
if r := recover(); r != nil {
// The imports map must contains all packages already imported.
// GcImport satisfies the ast.Importer signature.
//
-func GcImport(imports map[string]*ast.Object, path string) (pkg *ast.Object, err error) {
+func GcImport(imports map[string]*Package, path string) (pkg *Package, err error) {
if path == "unsafe" {
return Unsafe, nil
}
// object/archive file and populates its scope with the results.
type gcParser struct {
scanner scanner.Scanner
- tok rune // current token
- lit string // literal string; only valid for Ident, Int, String tokens
- id string // package id of imported package
- imports map[string]*ast.Object // package id -> package object
+ tok rune // current token
+ lit string // literal string; only valid for Ident, Int, String tokens
+ id string // package id of imported package
+ imports map[string]*Package // package id -> package object
}
-func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]*ast.Object) {
+func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]*Package) {
p.scanner.Init(src)
p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) }
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
}
}
-// Declare inserts a named object of the given kind in scope.
-func (p *gcParser) declare(scope *ast.Scope, kind ast.ObjKind, name string) *ast.Object {
- // the object may have been imported before - if it exists
- // already in the respective package scope, return that object
+func declConst(scope *Scope, name string) *Const {
+ // the constant may have been imported before - if it exists
+ // already in the respective scope, return that constant
if obj := scope.Lookup(name); obj != nil {
- assert(obj.Kind == kind)
- return obj
+ return obj.(*Const)
}
+ // otherwise create a new constant and insert it into the scope
+ obj := &Const{Name: name}
+ scope.Insert(obj)
+ return obj
+}
- // otherwise create a new object and insert it into the package scope
- obj := ast.NewObj(kind, name)
- if scope.Insert(obj) != nil {
- unreachable() // Lookup should have found it
+func declTypeName(scope *Scope, name string) *TypeName {
+ if obj := scope.Lookup(name); obj != nil {
+ return obj.(*TypeName)
}
+ obj := &TypeName{Name: name}
+ // a named type may be referred to before the underlying type
+ // is known - set it up
+ obj.Type = &NamedType{Obj: obj}
+ scope.Insert(obj)
+ return obj
+}
- // if the new type object is a named type it may be referred
- // to before the underlying type is known - set it up
- if kind == ast.Typ {
- obj.Type = &NamedType{Obj: obj}
+func declVar(scope *Scope, name string) *Var {
+ if obj := scope.Lookup(name); obj != nil {
+ return obj.(*Var)
}
+ obj := &Var{Name: name}
+ scope.Insert(obj)
+ return obj
+}
+func declFunc(scope *Scope, name string) *Func {
+ if obj := scope.Lookup(name); obj != nil {
+ return obj.(*Func)
+ }
+ obj := &Func{Name: name}
+ scope.Insert(obj)
return obj
}
// ImportPath = string_lit .
//
-func (p *gcParser) parsePkgId() *ast.Object {
+func (p *gcParser) parsePkgId() *Package {
id, err := strconv.Unquote(p.expect(scanner.String))
if err != nil {
p.error(err)
pkg := p.imports[id]
if pkg == nil {
- pkg = ast.NewObj(ast.Pkg, "")
- pkg.Data = ast.NewScope(nil)
+ pkg = &Package{Scope: new(Scope)}
p.imports[id] = pkg
}
// ExportedName = "@" ImportPath "." dotIdentifier .
//
-func (p *gcParser) parseExportedName() (*ast.Object, string) {
+func (p *gcParser) parseExportedName() (*Package, string) {
p.expect('@')
pkg := p.parsePkgId()
p.expect('.')
// Name = identifier | "?" | ExportedName .
//
-func (p *gcParser) parseName() (name string) {
+func (p *gcParser) parseName() (pkg *Package, name string) {
switch p.tok {
case scanner.Ident:
name = p.lit
p.next()
case '@':
// exported name prefixed with package path
- _, name = p.parseExportedName()
+ pkg, name = p.parseExportedName()
default:
p.error("name expected")
}
//
func (p *gcParser) parseField() *Field {
var f Field
- f.Name = p.parseName()
+ f.Pkg, f.Name = p.parseName()
f.Type = p.parseType()
if p.tok == scanner.String {
f.Tag = p.expect(scanner.String)
if f.Name == "" {
// anonymous field - typ must be T or *T and T must be a type name
if typ, ok := deref(f.Type).(*NamedType); ok && typ.Obj != nil {
- f.Name = typ.Obj.Name
+ f.Name = typ.Obj.GetName()
f.IsAnonymous = true
} else {
p.errorf("anonymous field expected")
// Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] .
//
func (p *gcParser) parseParameter() (par *Var, isVariadic bool) {
- name := p.parseName()
+ _, name := p.parseName()
if name == "" {
name = "_" // cannot access unnamed identifiers
}
if p.tok == scanner.String {
p.next()
}
- par = &Var{name, typ}
+ par = &Var{Name: name, Type: typ}
return
}
switch p.tok {
case scanner.Ident, '[', '*', '<', '@':
// single, unnamed result
- results = []*Var{{"", p.parseType()}}
+ results = []*Var{{Type: p.parseType()}}
case '(':
// named or multiple result(s)
var variadic bool
if len(methods) > 0 {
p.expect(';')
}
- name := p.parseName()
+ pkg, name := p.parseName()
typ := p.parseSignature()
- methods = append(methods, &Method{name, typ})
+ methods = append(methods, &Method{QualifiedName{pkg, name}, typ})
}
p.expect('}')
case '@':
// TypeName
pkg, name := p.parseExportedName()
- return p.declare(pkg.Data.(*ast.Scope), ast.Typ, name).Type.(Type)
+ return declTypeName(pkg.Scope, name).Type
case '[':
p.next() // look ahead
if p.tok == ']' {
func (p *gcParser) parseConstDecl() {
p.expectKeyword("const")
pkg, name := p.parseExportedName()
- obj := p.declare(pkg.Data.(*ast.Scope), ast.Con, name)
+ obj := declConst(pkg.Scope, name)
var x operand
if p.tok != '=' {
obj.Type = p.parseType()
obj.Type = x.typ
}
assert(x.val != nil)
- obj.Data = x.val
+ obj.Val = x.val
}
// TypeDecl = "type" ExportedName Type .
func (p *gcParser) parseTypeDecl() {
p.expectKeyword("type")
pkg, name := p.parseExportedName()
- obj := p.declare(pkg.Data.(*ast.Scope), ast.Typ, name)
+ obj := declTypeName(pkg.Scope, name)
// The type object may have been imported before and thus already
// have a type associated with it. We still need to parse the type
func (p *gcParser) parseVarDecl() {
p.expectKeyword("var")
pkg, name := p.parseExportedName()
- obj := p.declare(pkg.Data.(*ast.Scope), ast.Var, name)
+ obj := declVar(pkg.Scope, name)
obj.Type = p.parseType()
}
// Func = Signature [ Body ] .
// Body = "{" ... "}" .
//
-func (p *gcParser) parseFunc(scope *ast.Scope, name string) *Signature {
- obj := p.declare(scope, ast.Fun, name)
+func (p *gcParser) parseFunc() *Signature {
sig := p.parseSignature()
- obj.Type = sig
if p.tok == '{' {
p.next()
for i := 1; i > 0; p.next() {
p.expect(')')
// determine receiver base type object
- typ := recv.Type.(Type)
+ typ := recv.Type
if ptr, ok := typ.(*Pointer); ok {
typ = ptr.Base
}
- obj := typ.(*NamedType).Obj
-
- // determine base type scope
- var scope *ast.Scope
- if obj.Data != nil {
- scope = obj.Data.(*ast.Scope)
- } else {
- scope = ast.NewScope(nil)
- obj.Data = scope
- }
+ base := typ.(*NamedType)
- // declare method in base type scope
- name := p.parseName() // unexported method names in imports are qualified with their package.
- sig := p.parseFunc(scope, name)
+ // parse method name, signature, and possibly inlined body
+ pkg, name := p.parseName() // unexported method names in imports are qualified with their package.
+ sig := p.parseFunc()
sig.Recv = recv
+
+ // add method to type unless type was imported before
+ // and method exists already
+ // TODO(gri) investigate if this can be avoided
+ for _, m := range base.Methods {
+ if m.Name == name {
+ return // method was added before
+ }
+ }
+ base.Methods = append(base.Methods, &Method{QualifiedName{pkg, name}, sig})
}
// FuncDecl = "func" ExportedName Func .
func (p *gcParser) parseFuncDecl() {
// "func" already consumed
pkg, name := p.parseExportedName()
- p.parseFunc(pkg.Data.(*ast.Scope), name)
+ typ := p.parseFunc()
+ declFunc(pkg.Scope, name).Type = typ
}
// Decl = [ ImportDecl | ConstDecl | TypeDecl | VarDecl | FuncDecl | MethodDecl ] "\n" .
// Export = "PackageClause { Decl } "$$" .
// PackageClause = "package" identifier [ "safe" ] "\n" .
//
-func (p *gcParser) parseExport() *ast.Object {
+func (p *gcParser) parseExport() *Package {
p.expectKeyword("package")
name := p.expect(scanner.Ident)
if p.tok != '\n' {
pkg := p.imports[p.id]
if pkg == nil {
- pkg = ast.NewObj(ast.Pkg, name)
- pkg.Data = ast.NewScope(nil)
+ pkg = &Package{Name: name, Scope: new(Scope)}
p.imports[p.id] = pkg
}
// Use the same global imports map for all tests. The effect is
// as if all tested packages were imported into a single package.
-var imports = make(map[string]*ast.Object)
+var imports = make(map[string]*Package)
func testPath(t *testing.T, path string) bool {
t0 := time.Now()
continue
}
- obj := pkg.Data.(*ast.Scope).Lookup(objName)
- if obj.Kind != test.kind {
- t.Errorf("%s: got kind = %q; want %q", test.name, obj.Kind, test.kind)
+ obj := pkg.Scope.Lookup(objName)
+
+ // TODO(gri) should define an accessor on Object
+ var kind ast.ObjKind
+ var typ Type
+ switch obj := obj.(type) {
+ case *Const:
+ kind = ast.Con
+ typ = obj.Type
+ case *TypeName:
+ kind = ast.Typ
+ typ = obj.Type
+ case *Var:
+ kind = ast.Var
+ typ = obj.Type
+ case *Func:
+ kind = ast.Fun
+ typ = obj.Type
+ default:
+ unreachable()
}
- typ := typeString(underlying(obj.Type.(Type)))
- if typ != test.typ {
+
+ if kind != test.kind {
+ t.Errorf("%s: got kind = %q; want %q", test.name, kind, test.kind)
+ }
+
+ str := typeString(underlying(typ))
+ if str != test.typ {
t.Errorf("%s: got type = %q; want %q", test.name, typ, test.typ)
}
}
--- /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
+
+// 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
+}
+
+// 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
+}
+
+// A Const represents a declared constant.
+type Const struct {
+ implementsObject
+ Name string
+ Type Type
+ Val interface{}
+}
+
+// A TypeName represents a declared type.
+type TypeName struct {
+ implementsObject
+ Name string
+ Type Type // *NamedType or *Basic
+}
+
+// A Variable represents a declared variable (including function parameters and results).
+type Var struct {
+ implementsObject
+ Name string
+ Type Type
+}
+
+// A Func represents a declared function.
+type Func struct {
+ implementsObject
+ Name string
+ Type Type // *Signature or *Builtin
+}
+
+func (obj *Package) GetName() string { return obj.Name }
+func (obj *Const) GetName() string { return obj.Name }
+func (obj *TypeName) 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 *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]
+ }
+ for _, obj := range s.Elems {
+ 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.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
+ }
+ s.large[name] = obj
+ }
+ return nil
+}
}
// lookupFieldBreadthFirst searches all types in list for a single entry (field
-// or method) of the given name. If such a field is found, the result describes
-// the field mode and type; otherwise the result mode is invalid.
+// or method) of the given name from the given package. If such a field is found,
+// the result describes the field mode and type; otherwise the result mode is invalid.
// (This function is similar in structure to FieldByNameFunc in reflect/type.go)
//
-func lookupFieldBreadthFirst(list []embeddedType, name string) (res lookupResult) {
+func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res lookupResult) {
// visited records the types that have been searched already.
visited := make(map[*NamedType]bool)
visited[typ] = true
// look for a matching attached method
- if data := typ.Obj.Data; data != nil {
- if obj := data.(*ast.Scope).Lookup(name); obj != nil {
- assert(obj.Type != nil)
- if !potentialMatch(e.multiples, value, obj.Type.(Type)) {
+ if typ.obj != nil {
+ assert(typ.obj.Data == nil) // methods must have been moved to typ.Methods
+ }
+ for _, m := range typ.Methods {
+ if identicalNames(name, m.QualifiedName) {
+ assert(m.Type != nil)
+ if !potentialMatch(e.multiples, value, m.Type) {
return // name collision
}
}
}
- switch typ := underlying(typ).(type) {
+ switch t := typ.Underlying.(type) {
case *Struct:
// look for a matching field and collect embedded types
- for _, f := range typ.Fields {
- if f.Name == name {
+ for _, f := range t.Fields {
+ if identicalNames(name, f.QualifiedName) {
assert(f.Type != nil)
if !potentialMatch(e.multiples, variable, f.Type) {
return // name collision
case *Interface:
// look for a matching method
- for _, m := range typ.Methods {
- if m.Name == name {
+ for _, m := range t.Methods {
+ if identicalNames(name, m.QualifiedName) {
assert(m.Type != nil)
if !potentialMatch(e.multiples, value, m.Type) {
return // name collision
return nil
}
-func lookupField(typ Type, name string) (operandMode, Type) {
+func lookupField(typ Type, name QualifiedName) (operandMode, Type) {
typ = deref(typ)
- if typ, ok := typ.(*NamedType); ok {
- if data := typ.Obj.Data; data != nil {
- if obj := data.(*ast.Scope).Lookup(name); obj != nil {
- assert(obj.Type != nil)
- return value, obj.Type.(Type)
+ if t, ok := typ.(*NamedType); ok {
+ if t.obj != nil {
+ assert(t.obj.Data == nil) // methods must have been moved to t.Methods
+ }
+ for _, m := range t.Methods {
+ if identicalNames(name, m.QualifiedName) {
+ assert(m.Type != nil)
+ return value, m.Type
}
}
+ typ = t.Underlying
}
- switch typ := underlying(typ).(type) {
+ switch t := typ.(type) {
case *Struct:
var next []embeddedType
- for _, f := range typ.Fields {
- if f.Name == name {
+ for _, f := range t.Fields {
+ if identicalNames(name, f.QualifiedName) {
return variable, f.Type
}
if f.IsAnonymous {
}
case *Interface:
- for _, m := range typ.Methods {
- if m.Name == name {
+ for _, m := range t.Methods {
+ if identicalNames(name, m.QualifiedName) {
return value, m.Type
}
}
package types
+import "go/ast"
+
func isNamed(typ Type) bool {
if _, ok := typ.(*Basic); ok {
return ok
// and identical tags. Two anonymous fields are considered to have the same
// name. Lower-case field names from different packages are always different.
if y, ok := y.(*Struct); ok {
- // TODO(gri) handle structs from different packages
if len(x.Fields) == len(y.Fields) {
for i, f := range x.Fields {
g := y.Fields[i]
- if f.Name != g.Name ||
+ if !identicalNames(f.QualifiedName, g.QualifiedName) ||
!isIdentical(f.Type, g.Type) ||
f.Tag != g.Tag ||
f.IsAnonymous != g.IsAnonymous {
// Two named types are identical if their type names originate
// in the same type declaration.
if y, ok := y.(*NamedType); ok {
- return x.Obj == y.Obj
+ switch {
+ case x.obj != nil:
+ return x.obj == y.obj
+ case x.Obj != nil:
+ return x.Obj == y.Obj
+ default:
+ unreachable()
+ }
}
}
return false
}
+// identicalNames returns true if the names a and b are equal.
+func identicalNames(a, b QualifiedName) bool {
+ if a.Name != b.Name {
+ return false
+ }
+ // a.Name == b.Name
+ // TODO(gri) Guarantee that packages are canonicalized
+ // and then we can compare p == q directly.
+ return ast.IsExported(a.Name) || a.Pkg.Path == b.Pkg.Path
+}
+
// identicalTypes returns true if both lists a and b have the
// same length and corresponding objects have identical types.
func identicalTypes(a, b []*Var) bool {
if len(a) != len(b) {
return false
}
- m := make(map[string]*Method)
+ m := make(map[QualifiedName]*Method)
for _, x := range a {
- m[x.Name] = x
+ assert(m[x.QualifiedName] == nil) // method list must not have duplicate entries
+ m[x.QualifiedName] = x
}
for _, y := range b {
- if x := m[y.Name]; x == nil || !isIdentical(x.Type, y.Type) {
+ if x := m[y.QualifiedName]; x == nil || !isIdentical(x.Type, y.Type) {
return false
}
}
// is missing or simply has the wrong type.
//
func missingMethod(typ Type, T *Interface) (method *Method, wrongType bool) {
+ // TODO(gri): this needs to correctly compare method names (taking package into account)
// TODO(gri): distinguish pointer and non-pointer receivers
// an interface type implements T if it has no methods with conflicting signatures
// Note: This is stronger than the current spec. Should the spec require this?
if ityp, _ := underlying(typ).(*Interface); ityp != nil {
for _, m := range T.Methods {
- mode, sig := lookupField(ityp, m.Name) // TODO(gri) no need to go via lookupField
+ mode, sig := lookupField(ityp, m.QualifiedName) // TODO(gri) no need to go via lookupField
if mode != invalid && !isIdentical(sig, m.Type) {
return m, true
}
// a concrete type implements T if it implements all methods of T.
for _, m := range T.Methods {
- mode, sig := lookupField(typ, m.Name)
+ mode, sig := lookupField(typ, m.QualifiedName)
if mode == invalid {
return m, false
}
--- /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
+
+import (
+ "fmt"
+ "go/ast"
+ "strconv"
+)
+
+func (check *checker) declareObj(scope, altScope *ast.Scope, obj *ast.Object) {
+ alt := scope.Insert(obj)
+ if alt == nil && altScope != nil {
+ // see if there is a conflicting declaration in altScope
+ alt = altScope.Lookup(obj.Name)
+ }
+ if 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(), fmt.Sprintf("%s redeclared in this block%s", obj.Name, prevDecl))
+ }
+}
+
+func resolve(scope *ast.Scope, ident *ast.Ident) bool {
+ for ; scope != nil; scope = scope.Outer {
+ if obj := scope.Lookup(ident.Name); obj != nil {
+ ident.Obj = 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) {
+ // complete package scope
+ pkgName := ""
+ pkgScope := ast.NewScope(Universe)
+
+ i := 0
+ for _, file := range check.files {
+ // package names must match
+ switch name := file.Name.Name; {
+ case pkgName == "":
+ pkgName = name
+ case name != pkgName:
+ check.errorf(file.Package, "package %s; expected %s", name, pkgName)
+ continue // ignore this file
+ }
+
+ // keep this file
+ check.files[i] = file
+ i++
+
+ // collect top-level file objects in package scope
+ for _, obj := range file.Scope.Objects {
+ check.declareObj(pkgScope, nil, obj)
+ }
+ }
+ check.files = check.files[0:i]
+
+ // package global mapping of imported package ids to package objects
+ imports := make(map[string]*Package)
+
+ // complete file scopes with imports and resolve identifiers
+ for _, file := range check.files {
+ // build file scope by processing all imports
+ importErrors := false
+ fileScope := ast.NewScope(pkgScope)
+ for _, spec := range file.Imports {
+ if importer == nil {
+ importErrors = true
+ continue
+ }
+ path, _ := strconv.Unquote(spec.Path.Value)
+ pkg, err := importer(imports, path)
+ if err != nil {
+ check.errorf(spec.Path.Pos(), "could not import %s (%s)", path, err)
+ importErrors = true
+ continue
+ }
+ // TODO(gri) If a local package name != "." is provided,
+ // global identifier resolution could proceed even if the
+ // import failed. Consider adjusting the logic here a bit.
+
+ // local name overrides imported package name
+ name := pkg.Name
+ if spec.Name != nil {
+ name = spec.Name.Name
+ }
+
+ // 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)
+ }
+ */
+ } 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
+ check.declareObj(fileScope, pkgScope, obj)
+ }
+ }
+
+ // resolve identifiers
+ if importErrors {
+ // don't use the universe scope without correct imports
+ // (objects in the universe may be shadowed by imports;
+ // with missing imports, identifiers might get resolved
+ // incorrectly to universe objects)
+ pkgScope.Outer = nil
+ }
+ i := 0
+ for _, ident := range file.Unresolved {
+ if !resolve(fileScope, ident) {
+ check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
+ file.Unresolved[i] = ident
+ i++
+ }
+
+ }
+ file.Unresolved = file.Unresolved[0: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}
+}
func f() string {
return fmt.Sprintf("%d", g())
}
+ func g() (x int) { return }
`,
- `package p
- import . "go/parser"
- func g() Mode { return ImportsOnly }`,
+ // TODO(gri) fix this
+ // cannot handle dot-import at the moment
+ /*
+ `package p
+ import . "go/parser"
+ func g() Mode { return ImportsOnly }`,
+ */
}
var pkgnames = []string{
"fmt",
- "go/parser",
"math",
}
func TestResolveQualifiedIdents(t *testing.T) {
// parse package files
fset := token.NewFileSet()
- files := make(map[string]*ast.File)
+ files := make([]*ast.File, len(sources))
for i, src := range sources {
- filename := fmt.Sprintf("file%d", i)
- f, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
+ f, err := parser.ParseFile(fset, "", src, parser.DeclarationErrors)
if err != nil {
t.Fatal(err)
}
- files[filename] = f
+ files[i] = f
}
// resolve package AST
- pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
+ astpkg, pkg, err := Check(fset, files)
if err != nil {
t.Fatal(err)
}
}
}
+ // TODO(gri) fix this
+ // unresolved identifiers are not collected at the moment
// check that there are no top-level unresolved identifiers
- for _, f := range pkg.Files {
+ 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, pkg); err != nil {
+ if err := ResolveQualifiedIdents(fset, astpkg); err != nil {
t.Error(err)
}
// check that qualified identifiers are resolved
- ast.Inspect(pkg, func(n ast.Node) bool {
+ 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 {
Elt Type
}
+// A QualifiedName is a name qualified with the package the declared the name.
+type QualifiedName struct {
+ Pkg *Package // Pkg.Path == "" for current (non-imported) package
+ Name string // unqualified type name for anonymous fields
+}
+
// A Field represents a field of a struct.
type Field struct {
- Name string // unqualified type name for anonymous fields
+ QualifiedName
Type Type
Tag string
IsAnonymous bool
Base Type
}
-// A Variable represents a variable (including function parameters and results).
-type Var struct {
- Name string
- Type Type
-}
-
// A Result represents a (multi-value) function call result.
type Result struct {
implementsType
isStatement bool // true if the built-in is valid as an expression statement
}
-// A Method represents a method of an interface.
+// A Method represents a method.
type Method struct {
- Name string
+ QualifiedName
Type *Signature
}
// A NamedType represents a named type as declared in a type declaration.
type NamedType struct {
implementsType
- Obj *ast.Object // corresponding declared object; Obj.Data.(*ast.Scope) contains methods, if any
+ // TODO(gri) remove obj once we have moved away from ast.Objects
+ obj *ast.Object // corresponding declared object (current package)
+ Obj Object // corresponding declared object (imported 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
if err != nil {
return nil, err
}
- return Check(fset, map[string]*ast.File{filename: file})
+ astpkg, _, err := Check(fset, []*ast.File{file})
+ return astpkg, err
}
type testEntry struct {
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() })"
- pkg, err := makePkg(t, src)
+ file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
if err != nil {
t.Errorf("%s: %s", src, err)
continue
}
// TODO(gri) writing the code below w/o the decl variable will
// cause a 386 compiler error (out of fixed registers)
- decl := pkg.Files[filename].Decls[0].(*ast.GenDecl)
+ decl := file.Decls[0].(*ast.GenDecl)
expr := decl.Specs[0].(*ast.ValueSpec).Values[0]
str := exprString(expr)
if str != test.str {
)
var (
- aType implementsType
- Universe, unsafe *ast.Scope
- Unsafe *ast.Object // package unsafe
+ aType implementsType
+ Universe *ast.Scope
+ Unsafe *Package // package unsafe
)
// Predeclared types, indexed by BasicKind.
Universe = ast.NewScope(nil)
// unsafe package and its scope
- unsafe = ast.NewScope(nil)
- Unsafe = ast.NewObj(ast.Pkg, "unsafe")
- Unsafe.Data = unsafe
+ Unsafe = &Package{Name: "unsafe", Scope: new(Scope)}
// predeclared types
for _, t := range Typ {
- def(ast.Typ, t.Name).Type = t
+ def(ast.Typ, t.Name, t)
}
for _, t := range aliases {
- def(ast.Typ, t.Name).Type = t
+ def(ast.Typ, t.Name, t)
}
// error type
{
- err := &Method{"Error", &Signature{Results: []*Var{{"", Typ[String]}}}}
- obj := def(ast.Typ, "error")
- obj.Type = &NamedType{Underlying: &Interface{Methods: []*Method{err}}, Obj: obj}
+ err := &Method{QualifiedName{Name: "Error"}, &Signature{Results: []*Var{{Name: "", Type: Typ[String]}}}}
+ def(ast.Typ, "error", &NamedType{Underlying: &Interface{Methods: []*Method{err}}})
}
// predeclared constants
for _, t := range predeclaredConstants {
- obj := def(ast.Con, t.name)
- obj.Type = Typ[t.kind]
+ obj := def(ast.Con, t.name, Typ[t.kind])
obj.Data = t.val
}
// predeclared functions
for _, f := range predeclaredFunctions {
- def(ast.Fun, f.name).Type = f
+ def(ast.Fun, f.name, f)
}
universeIota = Universe.Lookup("iota")
// 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) *ast.Object {
- obj := ast.NewObj(kind, name)
+func def(kind ast.ObjKind, name string, typ Type) *ast.Object {
// insert non-internal objects into respective scope
if strings.Index(name, " ") < 0 {
- scope := Universe
// exported identifiers go into package unsafe
if ast.IsExported(name) {
- scope = unsafe
+ 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.obj = obj
+ }
+ if Universe.Insert(obj) != nil {
+ panic("internal error: double declaration")
+ }
+ return obj
}
- if scope.Insert(obj) != nil {
- panic("internal error: double declaration")
- }
- obj.Decl = scope
}
- return obj
+ return nil
}