Commit a87382e7 authored by Robert Griesemer's avatar Robert Griesemer

go/types: New Go type hierarchy implementation for AST.

This CL defines a new, more Go-like representation of
Go types (different structs for different types as
opposed to a single Type node). It also implements
an ast.Importer for object/archive files generated
by the gc compiler tool chain. Besides the individual
type structs, the main difference is the handling of
named types: In the old world, a named type had a
non-nil *Object pointer but otherwise looked no
different from other types. In this new model, named
types have their own representation types.Name. As
a result, resolving cycles is a bit simpler during
construction, at the cost of having to deal with
types.Name nodes explicitly later. It remains to be
seen if this is a good approach. Nevertheless, code
involving types reads more nicely and benefits from
full type checking. Also, the representation seems
to more closely match the spec wording.

Credits: The original version of the gc importer was
written by Evan Shaw (chickencha@gmail.com). The new
version in this CL is based largely on Evan's original
code but contains bug fixes, a few simplifications,
some restructuring, and was adjusted to use the
new type hierarchy. I have added a comprehensive test
that imports all packages found under $GOROOT/pkg (with
a 3s time-out to limit the run-time of the test). Run
gotest -v for details.

The original version of ExportData (exportdata.go) was
written by Russ Cox (rsc@golang.org). The current version
is returning the internal buffer positioned at the beginning
of the export data instead of printing the export data to
stdout.

With the new types package, the existing in-progress
typechecker package is deprecated. I will delete it
once all functionality has been brought over.

R=eds, rog, rsc
CC=golang-dev
https://golang.org/cl/4314054
parent 70bf4215
......@@ -90,6 +90,7 @@ DIRS=\
go/scanner\
go/token\
go/typechecker\
go/types\
gob\
hash\
hash/adler32\
......
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// INCOMPLETE PACKAGE.
// DEPRECATED PACKAGE - SEE go/types INSTEAD.
// This package implements typechecking of a Go AST.
// The result of the typecheck is an augmented AST
// with object and type information for each identifier.
......
# Copyright 2010 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
include ../../../Make.inc
TARG=go/types
GOFILES=\
const.go\
exportdata.go\
gcimporter.go\
types.go\
universe.go\
include ../../../Make.pkg
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements operations on ideal constants.
package types
import (
"big"
"go/token"
"strconv"
)
// TODO(gri) Consider changing the API so Const is an interface
// and operations on consts don't have to type switch.
// A Const implements an ideal constant Value.
// The zero value z for a Const is not a valid constant value.
type Const struct {
// representation of constant values:
// ideal bool -> bool
// ideal int -> *big.Int
// ideal float -> *big.Rat
// ideal complex -> cmplx
// ideal string -> string
val interface{}
}
// Representation of complex values.
type cmplx struct {
re, im *big.Rat
}
func assert(cond bool) {
if !cond {
panic("go/types internal error: assertion failed")
}
}
// MakeConst makes an ideal constant from a literal
// token and the corresponding literal string.
func MakeConst(tok token.Token, lit string) Const {
switch tok {
case token.INT:
var x big.Int
_, ok := x.SetString(lit, 0)
assert(ok)
return Const{&x}
case token.FLOAT:
var y big.Rat
_, ok := y.SetString(lit)
assert(ok)
return Const{&y}
case token.IMAG:
assert(lit[len(lit)-1] == 'i')
var im big.Rat
_, ok := im.SetString(lit[0 : len(lit)-1])
assert(ok)
return Const{cmplx{big.NewRat(0, 1), &im}}
case token.CHAR:
assert(lit[0] == '\'' && lit[len(lit)-1] == '\'')
code, _, _, err := strconv.UnquoteChar(lit[1:len(lit)-1], '\'')
assert(err == nil)
return Const{big.NewInt(int64(code))}
case token.STRING:
s, err := strconv.Unquote(lit)
assert(err == nil)
return Const{s}
}
panic("unreachable")
}
// MakeZero returns the zero constant for the given type.
func MakeZero(typ *Type) Const {
// TODO(gri) fix this
return Const{0}
}
// Match attempts to match the internal constant representations of x and y.
// If the attempt is successful, the result is the values of x and y,
// if necessary converted to have the same internal representation; otherwise
// the results are invalid.
func (x Const) Match(y Const) (u, v Const) {
switch a := x.val.(type) {
case bool:
if _, ok := y.val.(bool); ok {
u, v = x, y
}
case *big.Int:
switch y.val.(type) {
case *big.Int:
u, v = x, y
case *big.Rat:
var z big.Rat
z.SetInt(a)
u, v = Const{&z}, y
case cmplx:
var z big.Rat
z.SetInt(a)
u, v = Const{cmplx{&z, big.NewRat(0, 1)}}, y
}
case *big.Rat:
switch y.val.(type) {
case *big.Int:
v, u = y.Match(x)
case *big.Rat:
u, v = x, y
case cmplx:
u, v = Const{cmplx{a, big.NewRat(0, 0)}}, y
}
case cmplx:
switch y.val.(type) {
case *big.Int, *big.Rat:
v, u = y.Match(x)
case cmplx:
u, v = x, y
}
case string:
if _, ok := y.val.(string); ok {
u, v = x, y
}
default:
panic("unreachable")
}
return
}
// Convert attempts to convert the constant x to a given type.
// If the attempt is successful, the result is the new constant;
// otherwise the result is invalid.
func (x Const) Convert(typ *Type) Const {
// TODO(gri) implement this
switch x := x.val.(type) {
case bool:
case *big.Int:
case *big.Rat:
case cmplx:
case string:
}
return x
}
func (x Const) String() string {
switch x := x.val.(type) {
case bool:
if x {
return "true"
}
return "false"
case *big.Int:
return x.String()
case *big.Rat:
return x.FloatString(10) // 10 digits of precision after decimal point seems fine
case cmplx:
// TODO(gri) don't print 0 components
return x.re.FloatString(10) + " + " + x.im.FloatString(10) + "i"
case string:
return x
}
panic("unreachable")
}
func (x Const) UnaryOp(op token.Token) Const {
panic("unimplemented")
}
func (x Const) BinaryOp(op token.Token, y Const) Const {
var z interface{}
switch x := x.val.(type) {
case bool:
z = binaryBoolOp(x, op, y.val.(bool))
case *big.Int:
z = binaryIntOp(x, op, y.val.(*big.Int))
case *big.Rat:
z = binaryFloatOp(x, op, y.val.(*big.Rat))
case cmplx:
z = binaryCmplxOp(x, op, y.val.(cmplx))
case string:
z = binaryStringOp(x, op, y.val.(string))
default:
panic("unreachable")
}
return Const{z}
}
func binaryBoolOp(x bool, op token.Token, y bool) interface{} {
switch op {
case token.EQL:
return x == y
case token.NEQ:
return x != y
}
panic("unreachable")
}
func binaryIntOp(x *big.Int, op token.Token, y *big.Int) interface{} {
var z big.Int
switch op {
case token.ADD:
return z.Add(x, y)
case token.SUB:
return z.Sub(x, y)
case token.MUL:
return z.Mul(x, y)
case token.QUO:
return z.Quo(x, y)
case token.REM:
return z.Rem(x, y)
case token.AND:
return z.And(x, y)
case token.OR:
return z.Or(x, y)
case token.XOR:
return z.Xor(x, y)
case token.AND_NOT:
return z.AndNot(x, y)
case token.SHL:
panic("unimplemented")
case token.SHR:
panic("unimplemented")
case token.EQL:
return x.Cmp(y) == 0
case token.NEQ:
return x.Cmp(y) != 0
case token.LSS:
return x.Cmp(y) < 0
case token.LEQ:
return x.Cmp(y) <= 0
case token.GTR:
return x.Cmp(y) > 0
case token.GEQ:
return x.Cmp(y) >= 0
}
panic("unreachable")
}
func binaryFloatOp(x *big.Rat, op token.Token, y *big.Rat) interface{} {
var z big.Rat
switch op {
case token.ADD:
return z.Add(x, y)
case token.SUB:
return z.Sub(x, y)
case token.MUL:
return z.Mul(x, y)
case token.QUO:
return z.Quo(x, y)
case token.EQL:
return x.Cmp(y) == 0
case token.NEQ:
return x.Cmp(y) != 0
case token.LSS:
return x.Cmp(y) < 0
case token.LEQ:
return x.Cmp(y) <= 0
case token.GTR:
return x.Cmp(y) > 0
case token.GEQ:
return x.Cmp(y) >= 0
}
panic("unreachable")
}
func binaryCmplxOp(x cmplx, op token.Token, y cmplx) interface{} {
a, b := x.re, x.im
c, d := y.re, y.im
switch op {
case token.ADD:
// (a+c) + i(b+d)
var re, im big.Rat
re.Add(a, c)
im.Add(b, d)
return cmplx{&re, &im}
case token.SUB:
// (a-c) + i(b-d)
var re, im big.Rat
re.Sub(a, c)
im.Sub(b, d)
return cmplx{&re, &im}
case token.MUL:
// (ac-bd) + i(bc+ad)
var ac, bd, bc, ad big.Rat
ac.Mul(a, c)
bd.Mul(b, d)
bc.Mul(b, c)
ad.Mul(a, d)
var re, im big.Rat
re.Sub(&ac, &bd)
im.Add(&bc, &ad)
return cmplx{&re, &im}
case token.QUO:
// (ac+bd)/s + i(bc-ad)/s, with s = cc + dd
var ac, bd, bc, ad, s big.Rat
ac.Mul(a, c)
bd.Mul(b, d)
bc.Mul(b, c)
ad.Mul(a, d)
s.Add(c.Mul(c, c), d.Mul(d, d))
var re, im big.Rat
re.Add(&ac, &bd)
re.Quo(&re, &s)
im.Sub(&bc, &ad)
im.Quo(&im, &s)
return cmplx{&re, &im}
case token.EQL:
return a.Cmp(c) == 0 && b.Cmp(d) == 0
case token.NEQ:
return a.Cmp(c) != 0 || b.Cmp(d) != 0
}
panic("unreachable")
}
func binaryStringOp(x string, op token.Token, y string) interface{} {
switch op {
case token.ADD:
return x + y
case token.EQL:
return x == y
case token.NEQ:
return x != y
case token.LSS:
return x < y
case token.LEQ:
return x <= y
case token.GTR:
return x > y
case token.GEQ:
return x >= y
}
panic("unreachable")
}
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements ExportData.
package types
import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"strings"
)
func readGopackHeader(buf *bufio.Reader) (name string, size int, err os.Error) {
// See $GOROOT/include/ar.h.
hdr := make([]byte, 64+12+6+6+8+10+2)
_, err = io.ReadFull(buf, hdr)
if err != nil {
return
}
if trace {
fmt.Printf("header: %s", hdr)
}
s := strings.TrimSpace(string(hdr[64+12+6+6+8:][:10]))
size, err = strconv.Atoi(s)
if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' {
err = os.ErrorString("invalid archive header")
return
}
name = strings.TrimSpace(string(hdr[:64]))
return
}
type dataReader struct {
*bufio.Reader
io.Closer
}
// ExportData returns a readCloser positioned at the beginning of the
// export data section of the given object/archive file, or an error.
// It is the caller's responsibility to close the readCloser.
//
func ExportData(filename string) (rc io.ReadCloser, err os.Error) {
file, err := os.Open(filename)
if err != nil {
return
}
defer func() {
if err != nil {
file.Close()
// Add file name to error.
err = fmt.Errorf("reading export data: %s: %v", filename, err)
}
}()
buf := bufio.NewReader(file)
// Read first line to make sure this is an object file.
line, err := buf.ReadSlice('\n')
if err != nil {
return
}
if string(line) == "!<arch>\n" {
// Archive file. Scan to __.PKGDEF, which should
// be second archive entry.
var name string
var size int
// First entry should be __.SYMDEF.
// Read and discard.
if name, size, err = readGopackHeader(buf); err != nil {
return
}
if name != "__.SYMDEF" {
err = os.ErrorString("go archive does not begin with __.SYMDEF")
return
}
const block = 4096
tmp := make([]byte, block)
for size > 0 {
n := size
if n > block {
n = block
}
_, err = io.ReadFull(buf, tmp[:n])
if err != nil {
return
}
size -= n
}
// Second entry should be __.PKGDEF.
if name, size, err = readGopackHeader(buf); err != nil {
return
}
if name != "__.PKGDEF" {
err = os.ErrorString("go archive is missing __.PKGDEF")
return
}
// Read first line of __.PKGDEF data, so that line
// is once again the first line of the input.
line, err = buf.ReadSlice('\n')
if err != nil {
return
}
}
// Now at __.PKGDEF in archive or still at beginning of file.
// Either way, line should begin with "go object ".
if !strings.HasPrefix(string(line), "go object ") {
err = os.ErrorString("not a go object file")
return
}
// Skip over object header to export data.
// Begins after first line with $$.
for line[0] != '$' {
line, err = buf.ReadSlice('\n')
if err != nil {
return
}
}
rc = &dataReader{buf, file}
return
}
This diff is collapsed.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package types
import (
"exec"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
var gcName, gcPath string // compiler name and path
func init() {
// find a compiler
for _, char := range []string{"5", "6", "8"} {
var err os.Error
gcName = char + "g"
gcPath, err = exec.LookPath(gcName)
if err == nil {
return
}
}
}
func compile(t *testing.T, dirname, filename string) {
cmd, err := exec.Run(gcPath, []string{gcPath, filename}, nil, dirname, exec.DevNull, exec.Pipe, exec.MergeWithStdout)
if err != nil {
t.Errorf("%s %s failed: %s", gcName, filename, err)
return
}
defer cmd.Close()
msg, err := cmd.Wait(0)
if err != nil {
t.Errorf("%s %s failed: %s", gcName, filename, err)
return
}
if !msg.Exited() || msg.ExitStatus() != 0 {
t.Errorf("%s %s failed: exit status = %d", gcName, filename, msg.ExitStatus())
output, _ := ioutil.ReadAll(cmd.Stdout)
t.Log(string(output))
}
}
func testPath(t *testing.T, path string) bool {
_, _, err := GcImporter(path)
if err != nil {
t.Errorf("testPath(%s): %s", path, err)
return false
}
return true
}
const maxTime = 3e9 // maximum allotted testing time in ns
func testDir(t *testing.T, dir string, endTime int64) (nimports int) {
dirname := filepath.Join(pkgRoot, dir)
list, err := ioutil.ReadDir(dirname)
if err != nil {
t.Errorf("testDir(%s): %s", dirname, err)
}
for _, f := range list {
if time.Nanoseconds() >= endTime {
t.Log("testing time used up")
return
}
switch {
case f.IsRegular():
// try extensions
for _, ext := range pkgExts {
if strings.HasSuffix(f.Name, ext) {
name := f.Name[0 : len(f.Name)-len(ext)] // remove extension
if testPath(t, filepath.Join(dir, name)) {
nimports++
}
}
}
case f.IsDirectory():
nimports += testDir(t, filepath.Join(dir, f.Name), endTime)
}
}
return
}
func TestGcImport(t *testing.T) {
compile(t, "testdata", "exports.go")
nimports := 0
if testPath(t, "./testdata/exports") {
nimports++
}
nimports += testDir(t, "", time.Nanoseconds()+maxTime) // installed packages
t.Logf("tested %d imports", nimports)
}
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file is used to generate a .6 object file which
// serves as test file for gcimporter_test.go.
package exports
import (
"go/ast"
)
const (
C0 int = 0
C1 = 3.14159265
C2 = 2.718281828i
C3 = -123.456e-789
C4 = +123.456E+789
C5 = 1234i
C6 = "foo\n"
C7 = `bar\n`
)
type (
T1 int
T2 [10]int
T3 []int
T4 *int
T5 chan int
T6a chan<- int
T6b chan (<-chan int)
T6c chan<- (chan int)
T7 <-chan *ast.File
T8 struct{}
T9 struct {
a int
b, c float32
d []string "tag"
}
T10 struct {
T8
T9
_ *T10
}
T11 map[int]string
T12 interface{}
T13 interface {
m1()
m2(int) float32
}
T14 interface {
T12
T13
m3(x ...struct{}) []T9
}
T15 func()
T16 func(int)
T17 func(x int)
T18 func() float32
T19 func() (x float32)
T20 func(...interface{})
T21 struct{ next *T21 }
T22 struct{ link *T23 }
T23 struct{ link *T22 }
T24 *T24
T25 *T26
T26 *T27
T27 *T25
T28 func(T28) T28
)
var (
V0 int
V1 = -991.0
)
func F1() {}
func F2(x int) {}
func F3() int { return 0 }
func F4() float32 { return 0 }
func F5(a, b, c int, u, v, w struct{ x, y T1 }, more ...interface{}) (p, q, r chan<- T10)
func (p *T1) M1()
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// PACKAGE UNDER CONSTRUCTION. ANY AND ALL PARTS MAY CHANGE.
// The types package declares the types used to represent Go types.
//
package types
import "go/ast"
// All types implement the Type interface.
type Type interface {
isType()
}
// All concrete types embed ImplementsType which
// ensures that all types implement the Type interface.
type ImplementsType struct{}
func (t *ImplementsType) isType() {}
// A Basic represents a (unnamed) basic type.
type Basic struct {
ImplementsType
// TODO(gri) need a field specifying the exact basic type
}
// An Array represents an array type [Len]Elt.
type Array struct {
ImplementsType
Len uint64
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
// TODO(gri) need to remember fields.
}
// A Pointer represents a pointer type *Base.
type Pointer struct {
ImplementsType
Base Type
}
// A Func represents a function type func(...) (...).
type Func struct {
ImplementsType
IsVariadic bool
// TODO(gri) need to remember parameters.
}
// An Interface represents an interface type interface{...}.
type Interface struct {
ImplementsType
// TODO(gri) need to remember methods.
}
// 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 Name represents a named type as declared in a type declaration.
type Name struct {
ImplementsType
Underlying Type // nil if not fully declared
Obj *ast.Object // corresponding declared object
// TODO(gri) need to remember fields and methods.
}
// If typ is a pointer type, Deref returns the pointer's base type;
// otherwise it returns typ.
func Deref(typ Type) Type {
if typ, ok := typ.(*Pointer); ok {
return typ.Base
}
return typ
}
// Underlying returns the underlying type of a type.
func Underlying(typ Type) Type {
if typ, ok := typ.(*Name); ok {
utyp := typ.Underlying
if _, ok := utyp.(*Basic); ok {
return typ
}
return utyp
}
return typ
}
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// FILE UNDER CONSTRUCTION. ANY AND ALL PARTS MAY CHANGE.
// This file implements the universe and unsafe package scopes.
package types
import "go/ast"
var (
scope, // current scope to use for initialization
Universe,
Unsafe *ast.Scope
)
func define(kind ast.ObjKind, name string) *ast.Object {
obj := ast.NewObj(kind, name)
if scope.Insert(obj) != nil {
panic("types internal error: double declaration")
}
return obj
}
func defType(name string) *Name {
obj := define(ast.Typ, name)
typ := &Name{Underlying: &Basic{}, Obj: obj}
obj.Type = typ
return typ
}
func defConst(name string) {
obj := define(ast.Con, name)
_ = obj // TODO(gri) fill in other properties
}
func defFun(name string) {
obj := define(ast.Fun, name)
_ = obj // TODO(gri) fill in other properties
}
var (
Bool,
Int,
Float64,
Complex128,
String *Name
)
func init() {
Universe = ast.NewScope(nil)
scope = Universe
Bool = defType("bool")
defType("byte") // TODO(gri) should be an alias for uint8
defType("complex64")
Complex128 = defType("complex128")
defType("float32")
Float64 = defType("float64")
defType("int8")
defType("int16")
defType("int32")
defType("int64")
String = defType("string")
defType("uint8")
defType("uint16")
defType("uint32")
defType("uint64")
Int = defType("int")
defType("uint")
defType("uintptr")
defConst("true")
defConst("false")
defConst("iota")
defConst("nil")
defFun("append")
defFun("cap")
defFun("close")
defFun("complex")
defFun("copy")
defFun("imag")
defFun("len")
defFun("make")
defFun("new")
defFun("panic")
defFun("print")
defFun("println")
defFun("real")
defFun("recover")
Unsafe = ast.NewScope(nil)
scope = Unsafe
defType("Pointer")
defFun("Alignof")
defFun("New")
defFun("NewArray")
defFun("Offsetof")
defFun("Reflect")
defFun("Sizeof")
defFun("Typeof")
defFun("Unreflect")
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment