From a7f9d5d4f823e184bed26710a1f19cc116a9c29f Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 1 Oct 2014 09:56:28 -0700 Subject: [PATCH] go/types: use file not parse order for init order computation The type-checker depended on (token.Pos) position information of the presented files to determine source order. That information is determined by the parse order of the files rather than the order in which the files are presented to the type-checker. Introduced an order number strictly determined by the file order as presented to the type-checker and the AST structure of each file; thus providing source order information even in the absence of (token.Pos) position information. Added test case (provided by adonovan). LGTM=adonovan R=adonovan CC=golang-codereviews https://golang.org/cl/151160043 --- go/types/api_test.go | 33 +++++++++++++++++++++++++++++++++ go/types/initorder.go | 4 ++-- go/types/object.go | 29 +++++++++++++++++++++-------- go/types/ordering.go | 2 +- go/types/resolver.go | 2 ++ 5 files changed, 59 insertions(+), 11 deletions(-) diff --git a/go/types/api_test.go b/go/types/api_test.go index 0accef07..9b698a18 100644 --- a/go/types/api_test.go +++ b/go/types/api_test.go @@ -588,6 +588,39 @@ func TestInitOrderInfo(t *testing.T) { } } +func TestMultiFileInitOrder(t *testing.T) { + fset := token.NewFileSet() + mustParse := func(src string) *ast.File { + f, err := parser.ParseFile(fset, "main", src, 0) + if err != nil { + t.Fatal(err) + } + return f + } + + fileA := mustParse(`package main; var a = 1`) + fileB := mustParse(`package main; var b = 2`) + + // The initialization order must not depend on the parse + // order of the files, only on the presentation order to + // the type-checker. + for _, test := range []struct { + files []*ast.File + want string + }{ + {[]*ast.File{fileA, fileB}, "[a = 1 b = 2]"}, + {[]*ast.File{fileB, fileA}, "[b = 2 a = 1]"}, + } { + var info Info + if _, err := new(Config).Check("main", fset, test.files, &info); err != nil { + t.Fatal(err) + } + if got := fmt.Sprint(info.InitOrder); got != test.want { + t.Fatalf("got %s; want %s", got, test.want) + } + } +} + func TestFiles(t *testing.T) { var sources = []string{ "package p; type T struct{}; func (T) m1() {}", diff --git a/go/types/initorder.go b/go/types/initorder.go index 2d236100..0fd567b2 100644 --- a/go/types/initorder.go +++ b/go/types/initorder.go @@ -205,8 +205,8 @@ func (a nodeQueue) Swap(i, j int) { func (a nodeQueue) Less(i, j int) bool { x, y := a[i], a[j] // nodes are prioritized by number of incoming dependencies (1st key) - // and source positions (2nd key) - return x.in < y.in || x.in == y.in && x.obj.Pos() < y.obj.Pos() + // and source order (2nd key) + return x.in < y.in || x.in == y.in && x.obj.order() < y.obj.order() } func (a *nodeQueue) Push(x interface{}) { diff --git a/go/types/object.go b/go/types/object.go index 0bd59f16..6865d74a 100644 --- a/go/types/object.go +++ b/go/types/object.go @@ -31,9 +31,18 @@ type Object interface { // String returns a human-readable string of the object. String() string + // order reflects a package-level object's source order: if object + // a is before object b in the source, then a.order() < b.order(). + // order returns a value > 0 for package-level objects; it returns + // 0 for all other objects (including objects in file scopes). + order() uint32 + // isUsed reports whether the object was marked as 'used'. isUsed() bool + // setOrder sets the order number of the object. It must be > 0. + setOrder(uint32) + // setParent sets the parent scope of the object. setParent(*Scope) @@ -73,6 +82,8 @@ type object struct { pkg *Package name string typ Type + + order_ uint32 used bool } @@ -85,8 +96,10 @@ func (obj *object) Exported() bool { return ast.IsExported(obj.name) } func (obj *object) Id() string { return Id(obj.pkg, obj.name) } func (obj *object) String() string { panic("abstract") } -func (obj *object) isUsed() bool { return obj.used } +func (obj *object) order() uint32 { return obj.order_ } +func (obj *object) isUsed() bool { return obj.used } +func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order } func (obj *object) setParent(parent *Scope) { obj.parent = parent } func (obj *object) sameId(pkg *Package, name string) bool { @@ -118,7 +131,7 @@ type PkgName struct { } func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName { - return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], false}, imported} + return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, false}, imported} } // Imported returns the package that was imported. @@ -134,7 +147,7 @@ type Const struct { } func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val exact.Value) *Const { - return &Const{object: object{nil, pos, pkg, name, typ, false}, val: val} + return &Const{object: object{nil, pos, pkg, name, typ, 0, false}, val: val} } func (obj *Const) Val() exact.Value { return obj.val } @@ -145,7 +158,7 @@ type TypeName struct { } func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName { - return &TypeName{object{nil, pos, pkg, name, typ, false}} + return &TypeName{object{nil, pos, pkg, name, typ, 0, false}} } // A Variable represents a declared variable (including function parameters and results, and struct fields). @@ -158,15 +171,15 @@ type Var struct { } func NewVar(pos token.Pos, pkg *Package, name string, typ Type) *Var { - return &Var{object: object{nil, pos, pkg, name, typ, false}} + return &Var{object: object{nil, pos, pkg, name, typ, 0, false}} } func NewParam(pos token.Pos, pkg *Package, name string, typ Type) *Var { - return &Var{object: object{nil, pos, pkg, name, typ, true}} // parameters are always 'used' + return &Var{object: object{nil, pos, pkg, name, typ, 0, true}} // parameters are always 'used' } func NewField(pos token.Pos, pkg *Package, name string, typ Type, anonymous bool) *Var { - return &Var{object: object{nil, pos, pkg, name, typ, false}, anonymous: anonymous, isField: true} + return &Var{object: object{nil, pos, pkg, name, typ, 0, false}, anonymous: anonymous, isField: true} } func (obj *Var) Anonymous() bool { return obj.anonymous } @@ -186,7 +199,7 @@ func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func { if sig != nil { typ = sig } - return &Func{object{nil, pos, pkg, name, typ, false}} + return &Func{object{nil, pos, pkg, name, typ, 0, false}} } // FullName returns the package- or receiver-type-qualified name of diff --git a/go/types/ordering.go b/go/types/ordering.go index 0052f7c8..6bb98f2d 100644 --- a/go/types/ordering.go +++ b/go/types/ordering.go @@ -123,5 +123,5 @@ func orderedSetObjects(set map[Object]bool) []Object { type inSourceOrder []Object func (a inSourceOrder) Len() int { return len(a) } -func (a inSourceOrder) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() } +func (a inSourceOrder) Less(i, j int) bool { return a[i].order() < a[j].order() } func (a inSourceOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/go/types/resolver.go b/go/types/resolver.go index 4e0c48f4..db4f7dbb 100644 --- a/go/types/resolver.go +++ b/go/types/resolver.go @@ -110,6 +110,7 @@ func (check *Checker) declarePkgObj(ident *ast.Ident, obj Object, d *declInfo) { check.declare(check.pkg.scope, ident, obj) check.objMap[obj] = d + obj.setOrder(uint32(len(check.objMap))) } // collectObjects collects all file and package objects and inserts them @@ -341,6 +342,7 @@ func (check *Checker) collectObjects() { } info := &declInfo{file: fileScope, fdecl: d} check.objMap[obj] = info + obj.setOrder(uint32(len(check.objMap))) default: check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)