mirror of
https://github.com/golang/go
synced 2024-11-19 03:24:40 -07:00
go.tools/go/types: track init cycles through functions
- Info.InitOrder now provides list of Initializers (vars + init expr), handling n:1 decls and blank identifiers - added respective API test - cycles detected through function "mentions" Missing: cycles through method "mentions" and via closures R=adonovan CC=golang-dev https://golang.org/cl/21810043
This commit is contained in:
parent
964f0f559c
commit
9d1e9ed2ab
@ -162,11 +162,20 @@ type Info struct {
|
|||||||
//
|
//
|
||||||
Scopes map[ast.Node]*Scope
|
Scopes map[ast.Node]*Scope
|
||||||
|
|
||||||
// InitOrder is the list of package-level variables in the order in which
|
// InitOrder is the list of package-level initializers in the order in which
|
||||||
// they must be initialized. Variables related by an initialization dependency
|
// they must be executed. Initializers referring to variables related by an
|
||||||
// appear in topological order, and unrelated variables appear in source order.
|
// initialization dependency appear in topological order, the others appear
|
||||||
// TODO(gri) find a better name
|
// in source order. Variables without an initialization expression do not
|
||||||
InitOrder []*Var
|
// appear in this list.
|
||||||
|
InitOrder []*Initializer
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Initializer describes a package-level variable, or a list of variables in case
|
||||||
|
// of a multi-valued initialization expression, and the corresponding initialization
|
||||||
|
// expression.
|
||||||
|
type Initializer struct {
|
||||||
|
Lhs []*Var // var Lhs = Rhs
|
||||||
|
Rhs ast.Expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check type-checks a package and returns the resulting package object,
|
// Check type-checks a package and returns the resulting package object,
|
||||||
|
@ -209,3 +209,50 @@ func TestScopesInfo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInitOrder(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
src string
|
||||||
|
vars [][]string
|
||||||
|
// TODO(gri) check also init expression
|
||||||
|
}{
|
||||||
|
{`package p; var (x = 1; y = x)`, [][]string{{"x"}, {"y"}}},
|
||||||
|
{`package p; var (a = 1; b = 2; c = 3)`, [][]string{{"a"}, {"b"}, {"c"}}},
|
||||||
|
{`package p; var (a, b, c = 1, 2, 3)`, [][]string{{"a"}, {"b"}, {"c"}}},
|
||||||
|
{`package p; var _ = f(); func f() int { return 1 }`, [][]string{{"_"}}}, // blank var
|
||||||
|
{`package p; var (a = 0; x = y; y = z; z = 0)`, [][]string{{"a"}, {"z"}, {"y"}, {"x"}}},
|
||||||
|
{`package p; var (a, _ = m[0]; m map[int]string)`, [][]string{{"a", "_"}}}, // blank var
|
||||||
|
{`package p; var a, b = f(); func f() (_, _ int) { return z, z }; var z = 0`, [][]string{{"z"}, {"a", "b"}}},
|
||||||
|
// TODO(gri) add more tests (incl. methods, closures, init functions)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
path := fmt.Sprintf("InitOrder%d", i)
|
||||||
|
info := Info{}
|
||||||
|
mustTypecheck(t, path, test.src, &info)
|
||||||
|
|
||||||
|
// number of initializers must match
|
||||||
|
if len(info.InitOrder) != len(test.vars) {
|
||||||
|
t.Errorf("%s: got %d initializers; want %d", path, len(info.InitOrder), len(test.vars))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// initializer order and initialized variables must match
|
||||||
|
for i, got := range info.InitOrder {
|
||||||
|
want := test.vars[i]
|
||||||
|
// number of variables must match
|
||||||
|
if len(got.Lhs) != len(want) {
|
||||||
|
t.Errorf("%s, %d.th initializer: got %d variables; want %d", path, i, len(got.Lhs), len(want))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// variable names must match
|
||||||
|
for j, got := range got.Lhs {
|
||||||
|
want := want[j]
|
||||||
|
if got.name != want {
|
||||||
|
t.Errorf("%s, %d.th initializer: got name %s; want %s", path, i, got.name, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -34,20 +34,20 @@ type checker struct {
|
|||||||
fset *token.FileSet
|
fset *token.FileSet
|
||||||
pkg *Package
|
pkg *Package
|
||||||
|
|
||||||
methods map[string][]*Func // maps type names to associated methods
|
methods map[string][]*Func // maps type names to associated methods
|
||||||
conversions map[*ast.CallExpr]bool // set of type-checked conversions (to distinguish from calls)
|
conversions map[*ast.CallExpr]bool // set of type-checked conversions (to distinguish from calls)
|
||||||
dependencies map[Object]map[Object]bool // set of object initialization dependencies (obj depends on {obj1, obj2, ...})
|
untyped map[ast.Expr]exprInfo // map of expressions without final type
|
||||||
untyped map[ast.Expr]exprInfo // map of expressions without final type
|
lhsVarsList [][]*Var // type switch lhs variable sets, for 'declared but not used' errors
|
||||||
lhsVarsList [][]*Var // type switch lhs variable sets, for 'declared but not used' errors
|
delayed []func() // delayed checks that require fully setup types
|
||||||
delayed []func() // delayed checks that require fully setup types
|
|
||||||
|
|
||||||
firstErr error // first error encountered
|
firstErr error // first error encountered
|
||||||
Info // collected type info
|
Info // collected type info
|
||||||
|
|
||||||
objMap map[Object]declInfo // if set we are in the package-level declaration phase (otherwise all objects seen must be declared)
|
objMap map[Object]*declInfo // if set we are in the package-level declaration phase (otherwise all objects seen must be declared)
|
||||||
topScope *Scope // current topScope for lookups
|
initMap map[Object]*declInfo // map of variables/functions with init expressions/bodies
|
||||||
iota exact.Value // current value of iota in a constant declaration; nil otherwise
|
topScope *Scope // current topScope for lookups
|
||||||
init Object // current variable or function for which init expression or body is type-checked
|
iota exact.Value // current value of iota in a constant declaration; nil otherwise
|
||||||
|
decl *declInfo // current package-level declaration whose init expression/body is type-checked
|
||||||
|
|
||||||
// functions
|
// functions
|
||||||
funcList []funcInfo // list of functions/methods with correct signatures and non-empty bodies
|
funcList []funcInfo // list of functions/methods with correct signatures and non-empty bodies
|
||||||
@ -60,43 +60,32 @@ type checker struct {
|
|||||||
|
|
||||||
func newChecker(conf *Config, fset *token.FileSet, pkg *Package) *checker {
|
func newChecker(conf *Config, fset *token.FileSet, pkg *Package) *checker {
|
||||||
return &checker{
|
return &checker{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
fset: fset,
|
fset: fset,
|
||||||
pkg: pkg,
|
pkg: pkg,
|
||||||
methods: make(map[string][]*Func),
|
methods: make(map[string][]*Func),
|
||||||
conversions: make(map[*ast.CallExpr]bool),
|
conversions: make(map[*ast.CallExpr]bool),
|
||||||
dependencies: make(map[Object]map[Object]bool),
|
untyped: make(map[ast.Expr]exprInfo),
|
||||||
untyped: make(map[ast.Expr]exprInfo),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isVarOrFunc(obj Object) bool {
|
// addDeclDep adds the dependency edge (check.decl -> to)
|
||||||
switch obj.(type) {
|
// if check.decl exists and to has an init expression.
|
||||||
case *Var, *Func:
|
func (check *checker) addDeclDep(to Object) {
|
||||||
return true
|
from := check.decl
|
||||||
|
if from == nil {
|
||||||
|
return // not in a package-level init expression
|
||||||
}
|
}
|
||||||
return false
|
init := check.initMap[to]
|
||||||
}
|
if init == nil {
|
||||||
|
return // to does not have a package-level init expression
|
||||||
// addInitDep adds the dependency edge (to -> check.init)
|
|
||||||
// if check.init is a package-level variable or function.
|
|
||||||
func (check *checker) addInitDep(to Object) {
|
|
||||||
from := check.init
|
|
||||||
if from == nil || from.Parent() == nil {
|
|
||||||
return // not a package-level variable or function
|
|
||||||
}
|
}
|
||||||
|
m := from.deps
|
||||||
if debug {
|
|
||||||
assert(isVarOrFunc(from))
|
|
||||||
assert(isVarOrFunc(to))
|
|
||||||
}
|
|
||||||
|
|
||||||
m := check.dependencies[from] // lazily allocated
|
|
||||||
if m == nil {
|
if m == nil {
|
||||||
m = make(map[Object]bool)
|
m = make(map[Object]*declInfo)
|
||||||
check.dependencies[from] = m
|
from.deps = m
|
||||||
}
|
}
|
||||||
m[to] = true
|
m[to] = init
|
||||||
}
|
}
|
||||||
|
|
||||||
func (check *checker) delay(f func()) {
|
func (check *checker) delay(f func()) {
|
||||||
|
@ -969,7 +969,8 @@ func (check *checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
|
|||||||
if sig, ok := check.typ(e.Type, nil, false).(*Signature); ok {
|
if sig, ok := check.typ(e.Type, nil, false).(*Signature); ok {
|
||||||
x.mode = value
|
x.mode = value
|
||||||
x.typ = sig
|
x.typ = sig
|
||||||
check.later(nil, sig, e.Body)
|
// TODO(gri) provide correct *declInfo here
|
||||||
|
check.later(nil, nil, sig, e.Body)
|
||||||
} else {
|
} else {
|
||||||
check.invalidAST(e.Pos(), "invalid function literal %s", e)
|
check.invalidAST(e.Pos(), "invalid function literal %s", e)
|
||||||
goto Error
|
goto Error
|
||||||
|
@ -37,31 +37,27 @@ func (check *checker) declare(scope *Scope, id *ast.Ident, obj Object) {
|
|||||||
// A declInfo describes a package-level const, type, var, or func declaration.
|
// A declInfo describes a package-level const, type, var, or func declaration.
|
||||||
type declInfo struct {
|
type declInfo struct {
|
||||||
file *Scope // scope of file containing this declaration
|
file *Scope // scope of file containing this declaration
|
||||||
|
lhs []*Var // lhs of n:1 variable declarations, or nil
|
||||||
typ ast.Expr // type, or nil
|
typ ast.Expr // type, or nil
|
||||||
init ast.Expr // initialization expression, or nil
|
init ast.Expr // init expression, or nil
|
||||||
fdecl *ast.FuncDecl // function declaration, or nil
|
fdecl *ast.FuncDecl // func declaration, or nil
|
||||||
}
|
|
||||||
|
|
||||||
// A multiExpr describes the lhs variables and a single but
|
deps map[Object]*declInfo // init dependencies; lazily allocated
|
||||||
// (expected to be) multi-valued rhs init expr of a variable
|
mark byte // see check.dependencies
|
||||||
// declaration.
|
|
||||||
type multiExpr struct {
|
|
||||||
lhs []*Var
|
|
||||||
rhs []ast.Expr // len(rhs) == 1
|
|
||||||
ast.Expr // dummy to satisfy ast.Expr interface
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type funcInfo struct {
|
type funcInfo struct {
|
||||||
obj *Func // for debugging/tracing only
|
obj *Func // for debugging/tracing only
|
||||||
|
info *declInfo // for cycle detection
|
||||||
sig *Signature
|
sig *Signature
|
||||||
body *ast.BlockStmt
|
body *ast.BlockStmt
|
||||||
}
|
}
|
||||||
|
|
||||||
// later appends a function with non-empty body to check.funcList.
|
// later appends a function with non-empty body to check.funcList.
|
||||||
func (check *checker) later(f *Func, sig *Signature, body *ast.BlockStmt) {
|
func (check *checker) later(f *Func, info *declInfo, sig *Signature, body *ast.BlockStmt) {
|
||||||
// functions implemented elsewhere (say in assembly) have no body
|
// functions implemented elsewhere (say in assembly) have no body
|
||||||
if !check.conf.IgnoreFuncBodies && body != nil {
|
if !check.conf.IgnoreFuncBodies && body != nil {
|
||||||
check.funcList = append(check.funcList, funcInfo{f, sig, body})
|
check.funcList = append(check.funcList, funcInfo{f, info, sig, body})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,14 +103,14 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||||||
// base type names.
|
// base type names.
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fileScope *Scope // current file scope
|
objList []Object // list of package-level objects, in source order
|
||||||
objList []Object // list of package-level objects to type-check
|
objMap = make(map[Object]*declInfo) // corresponding declaration info
|
||||||
objMap = make(map[Object]declInfo) // declaration info for each package-level object
|
initMap = make(map[Object]*declInfo) // declaration info for variables with initializers
|
||||||
)
|
)
|
||||||
|
|
||||||
// declare declares obj in the package scope, records its ident -> obj mapping,
|
// declare declares obj in the package scope, records its ident -> obj mapping,
|
||||||
// and updates objList and objMap. The object must not be a function or method.
|
// and updates objList and objMap. The object must not be a function or method.
|
||||||
declare := func(ident *ast.Ident, obj Object, typ, init ast.Expr) {
|
declare := func(ident *ast.Ident, obj Object, d *declInfo) {
|
||||||
assert(ident.Name == obj.Name())
|
assert(ident.Name == obj.Name())
|
||||||
|
|
||||||
// spec: "A package-scope or file-scope identifier with name init
|
// spec: "A package-scope or file-scope identifier with name init
|
||||||
@ -126,7 +122,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||||||
|
|
||||||
check.declare(pkg.scope, ident, obj)
|
check.declare(pkg.scope, ident, obj)
|
||||||
objList = append(objList, obj)
|
objList = append(objList, obj)
|
||||||
objMap[obj] = declInfo{fileScope, typ, init, nil}
|
objMap[obj] = d
|
||||||
}
|
}
|
||||||
|
|
||||||
importer := check.conf.Import
|
importer := check.conf.Import
|
||||||
@ -145,7 +141,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||||||
// but there is no corresponding package object.
|
// but there is no corresponding package object.
|
||||||
check.recordObject(file.Name, nil)
|
check.recordObject(file.Name, nil)
|
||||||
|
|
||||||
fileScope = NewScope(pkg.scope)
|
fileScope := NewScope(pkg.scope)
|
||||||
check.recordScope(file, fileScope)
|
check.recordScope(file, fileScope)
|
||||||
fileScopes = append(fileScopes, fileScope)
|
fileScopes = append(fileScopes, fileScope)
|
||||||
dotImports = append(dotImports, nil) // element (map) is lazily allocated
|
dotImports = append(dotImports, nil) // element (map) is lazily allocated
|
||||||
@ -253,36 +249,46 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||||||
init = last.Values[i]
|
init = last.Values[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
declare(name, obj, last.Type, init)
|
declare(name, obj, &declInfo{file: fileScope, typ: last.Type, init: init})
|
||||||
}
|
}
|
||||||
|
|
||||||
check.arityMatch(s, last)
|
check.arityMatch(s, last)
|
||||||
|
|
||||||
case token.VAR:
|
case token.VAR:
|
||||||
// declare all variables
|
|
||||||
lhs := make([]*Var, len(s.Names))
|
lhs := make([]*Var, len(s.Names))
|
||||||
|
// If there's exactly one rhs initializer, use
|
||||||
|
// the same declInfo d1 for all lhs variables
|
||||||
|
// so that each lhs variable depends on the same
|
||||||
|
// rhs initializer (n:1 var declaration).
|
||||||
|
var d1 *declInfo
|
||||||
|
if len(s.Values) == 1 {
|
||||||
|
// The lhs elements are only set up after the foor loop below,
|
||||||
|
// but that's ok because declareVar only collects the declInfo
|
||||||
|
// for a later phase.
|
||||||
|
d1 = &declInfo{file: fileScope, lhs: lhs, typ: s.Type, init: s.Values[0]}
|
||||||
|
}
|
||||||
|
|
||||||
|
// declare all variables
|
||||||
for i, name := range s.Names {
|
for i, name := range s.Names {
|
||||||
obj := NewVar(name.Pos(), pkg, name.Name, nil)
|
obj := NewVar(name.Pos(), pkg, name.Name, nil)
|
||||||
lhs[i] = obj
|
lhs[i] = obj
|
||||||
|
|
||||||
|
d := d1
|
||||||
var init ast.Expr
|
var init ast.Expr
|
||||||
switch len(s.Values) {
|
if d == nil {
|
||||||
case len(s.Names):
|
// individual assignments
|
||||||
// lhs and rhs match
|
|
||||||
init = s.Values[i]
|
|
||||||
case 1:
|
|
||||||
// rhs must be a multi-valued expression
|
|
||||||
// (lhs may not be fully set up yet, but
|
|
||||||
// that's fine because declare simply collects
|
|
||||||
// the information for later processing.)
|
|
||||||
init = &multiExpr{lhs, s.Values, nil}
|
|
||||||
default:
|
|
||||||
if i < len(s.Values) {
|
if i < len(s.Values) {
|
||||||
init = s.Values[i]
|
init = s.Values[i]
|
||||||
}
|
}
|
||||||
|
d = &declInfo{file: fileScope, typ: s.Type, init: init}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare(name, obj, s.Type, init)
|
declare(name, obj, d)
|
||||||
|
|
||||||
|
// remember obj if it has an initializer
|
||||||
|
if d1 != nil || init != nil {
|
||||||
|
initMap[obj] = d
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
check.arityMatch(s, nil)
|
check.arityMatch(s, nil)
|
||||||
@ -293,7 +299,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||||||
|
|
||||||
case *ast.TypeSpec:
|
case *ast.TypeSpec:
|
||||||
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
|
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
|
||||||
declare(s.Name, obj, s.Type, nil)
|
declare(s.Name, obj, &declInfo{file: fileScope, typ: s.Type})
|
||||||
|
|
||||||
default:
|
default:
|
||||||
check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s)
|
check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s)
|
||||||
@ -333,7 +339,12 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
objList = append(objList, obj)
|
objList = append(objList, obj)
|
||||||
objMap[obj] = declInfo{fileScope, nil, nil, d}
|
info := &declInfo{file: fileScope, fdecl: d}
|
||||||
|
objMap[obj] = info
|
||||||
|
// remember obj if it has a body (= initializer)
|
||||||
|
if d.Body != nil {
|
||||||
|
initMap[obj] = info
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
|
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
|
||||||
@ -354,6 +365,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||||||
// Phase 3: Typecheck all objects in objList, but not function bodies.
|
// Phase 3: Typecheck all objects in objList, but not function bodies.
|
||||||
|
|
||||||
check.objMap = objMap // indicate that we are checking package-level declarations (objects may not have a type yet)
|
check.objMap = objMap // indicate that we are checking package-level declarations (objects may not have a type yet)
|
||||||
|
check.initMap = initMap
|
||||||
for _, obj := range objList {
|
for _, obj := range objList {
|
||||||
if obj.Type() == nil {
|
if obj.Type() == nil {
|
||||||
check.objDecl(obj, nil, false)
|
check.objDecl(obj, nil, false)
|
||||||
@ -387,6 +399,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||||||
check.topScope = f.sig.scope // open function scope
|
check.topScope = f.sig.scope // open function scope
|
||||||
check.funcSig = f.sig
|
check.funcSig = f.sig
|
||||||
check.hasLabel = false
|
check.hasLabel = false
|
||||||
|
check.decl = f.info
|
||||||
check.stmtList(0, f.body.List)
|
check.stmtList(0, f.body.List)
|
||||||
|
|
||||||
if check.hasLabel {
|
if check.hasLabel {
|
||||||
@ -402,13 +415,17 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||||||
// Note: must happen after checking all functions because function bodies
|
// Note: must happen after checking all functions because function bodies
|
||||||
// may introduce dependencies
|
// may introduce dependencies
|
||||||
|
|
||||||
state := make(map[Object]uint8)
|
// For source order and reproducible error messages,
|
||||||
|
// iterate through objList rather than initMap.
|
||||||
for _, obj := range objList {
|
for _, obj := range objList {
|
||||||
if isVarOrFunc(obj) && state[obj] == 0 {
|
if obj, _ := obj.(*Var); obj != nil {
|
||||||
check.objDependencies(obj, state)
|
if init := initMap[obj]; init != nil {
|
||||||
|
check.dependencies(obj, init)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
objList = nil // not needed anymore
|
objList = nil // not needed anymore
|
||||||
|
check.initMap = nil // not needed anymore
|
||||||
|
|
||||||
// Phase 6: Check for declared but not used packages and variables.
|
// Phase 6: Check for declared but not used packages and variables.
|
||||||
// Note: must happen after checking all functions because closures may affect outer scopes
|
// Note: must happen after checking all functions because closures may affect outer scopes
|
||||||
@ -473,23 +490,36 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (check *checker) objDependencies(obj Object, state map[Object]uint8) {
|
func (check *checker) dependencies(obj Object, init *declInfo) {
|
||||||
const inProgress, done = 1, 2
|
const inProgress, done = 1, 2
|
||||||
if state[obj] == inProgress {
|
|
||||||
check.errorf(obj.Pos(), "initialization cycle for %s", obj.Name())
|
if init.mark == done {
|
||||||
// TODO(gri) print the cycle
|
|
||||||
state[obj] = done // avoid further errors
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state[obj] = inProgress
|
if init.mark == inProgress {
|
||||||
for dep := range check.dependencies[obj] {
|
// cycle detected: end recursion and report error for variables
|
||||||
check.objDependencies(dep, state)
|
if obj, _ := obj.(*Var); obj != nil {
|
||||||
|
check.errorf(obj.pos, "initialization cycle for %s", obj.name)
|
||||||
|
// TODO(gri) print the cycle
|
||||||
|
init.mark = done // avoid further errors
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
state[obj] = done
|
|
||||||
|
|
||||||
if v, _ := obj.(*Var); v != nil {
|
init.mark = inProgress
|
||||||
check.Info.InitOrder = append(check.Info.InitOrder, v)
|
for obj, dep := range init.deps {
|
||||||
|
check.dependencies(obj, dep)
|
||||||
|
}
|
||||||
|
init.mark = done
|
||||||
|
|
||||||
|
// record the init order for variables
|
||||||
|
if lhs, _ := obj.(*Var); lhs != nil {
|
||||||
|
initLhs := init.lhs // possibly nil (see declInfo.lhs field comment)
|
||||||
|
if initLhs == nil {
|
||||||
|
initLhs = []*Var{lhs}
|
||||||
|
}
|
||||||
|
check.Info.InitOrder = append(check.Info.InitOrder, &Initializer{initLhs, init.init})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,19 +547,25 @@ func (check *checker) objDecl(obj Object, def *Named, cycleOk bool) {
|
|||||||
oldIota := check.iota
|
oldIota := check.iota
|
||||||
check.iota = nil
|
check.iota = nil
|
||||||
|
|
||||||
|
// save current decl
|
||||||
|
oldDecl := check.decl
|
||||||
|
check.decl = nil
|
||||||
|
|
||||||
switch obj := obj.(type) {
|
switch obj := obj.(type) {
|
||||||
case *Const:
|
case *Const:
|
||||||
check.constDecl(obj, d.typ, d.init)
|
check.constDecl(obj, d.typ, d.init)
|
||||||
case *Var:
|
case *Var:
|
||||||
check.varDecl(obj, d.typ, d.init)
|
check.decl = d // new package-level var decl
|
||||||
|
check.varDecl(obj, d.lhs, d.typ, d.init)
|
||||||
case *TypeName:
|
case *TypeName:
|
||||||
check.typeDecl(obj, d.typ, def, cycleOk)
|
check.typeDecl(obj, d.typ, def, cycleOk)
|
||||||
case *Func:
|
case *Func:
|
||||||
check.funcDecl(obj, d.fdecl)
|
check.funcDecl(obj, d)
|
||||||
default:
|
default:
|
||||||
unreachable()
|
unreachable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check.decl = oldDecl
|
||||||
check.iota = oldIota
|
check.iota = oldIota
|
||||||
check.topScope = oldScope
|
check.topScope = oldScope
|
||||||
}
|
}
|
||||||
@ -561,7 +597,8 @@ func (check *checker) constDecl(obj *Const, typ, init ast.Expr) {
|
|||||||
check.iota = nil
|
check.iota = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (check *checker) varDecl(obj *Var, typ, init ast.Expr) {
|
// TODO(gri) document arguments
|
||||||
|
func (check *checker) varDecl(obj *Var, lhs []*Var, typ, init ast.Expr) {
|
||||||
if obj.visited {
|
if obj.visited {
|
||||||
check.errorf(obj.Pos(), "illegal cycle in initialization of variable %s", obj.name)
|
check.errorf(obj.Pos(), "illegal cycle in initialization of variable %s", obj.name)
|
||||||
obj.typ = Typ[Invalid]
|
obj.typ = Typ[Invalid]
|
||||||
@ -586,19 +623,28 @@ func (check *checker) varDecl(obj *Var, typ, init ast.Expr) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// save current init object
|
if lhs == nil || len(lhs) == 1 {
|
||||||
oldInit := check.init
|
assert(lhs == nil || lhs[0] == obj)
|
||||||
check.init = obj
|
|
||||||
|
|
||||||
if m, _ := init.(*multiExpr); m != nil {
|
|
||||||
check.initVars(m.lhs, m.rhs, token.NoPos)
|
|
||||||
} else {
|
|
||||||
var x operand
|
var x operand
|
||||||
check.expr(&x, init)
|
check.expr(&x, init)
|
||||||
check.initVar(obj, &x)
|
check.initVar(obj, &x)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
check.init = oldInit
|
if debug {
|
||||||
|
// obj must be one of lhs
|
||||||
|
found := false
|
||||||
|
for _, lhs := range lhs {
|
||||||
|
if obj == lhs {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
panic("inconsistent lhs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check.initVars(lhs, []ast.Expr{init}, token.NoPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (check *checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, cycleOk bool) {
|
func (check *checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, cycleOk bool) {
|
||||||
@ -696,11 +742,12 @@ func (check *checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, cycleOk
|
|||||||
delete(check.methods, obj.name) // we don't need them anymore
|
delete(check.methods, obj.name) // we don't need them anymore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (check *checker) funcDecl(obj *Func, fdecl *ast.FuncDecl) {
|
func (check *checker) funcDecl(obj *Func, info *declInfo) {
|
||||||
// func declarations cannot use iota
|
// func declarations cannot use iota
|
||||||
assert(check.iota == nil)
|
assert(check.iota == nil)
|
||||||
|
|
||||||
obj.typ = Typ[Invalid] // guard against cycles
|
obj.typ = Typ[Invalid] // guard against cycles
|
||||||
|
fdecl := info.fdecl
|
||||||
sig := check.funcType(fdecl.Recv, fdecl.Type, nil)
|
sig := check.funcType(fdecl.Recv, fdecl.Type, nil)
|
||||||
if sig.recv == nil && obj.name == "init" && (sig.params.Len() > 0 || sig.results.Len() > 0) {
|
if sig.recv == nil && obj.name == "init" && (sig.params.Len() > 0 || sig.results.Len() > 0) {
|
||||||
check.errorf(fdecl.Pos(), "func init must have no arguments and no return values")
|
check.errorf(fdecl.Pos(), "func init must have no arguments and no return values")
|
||||||
@ -708,7 +755,7 @@ func (check *checker) funcDecl(obj *Func, fdecl *ast.FuncDecl) {
|
|||||||
}
|
}
|
||||||
obj.typ = sig
|
obj.typ = sig
|
||||||
|
|
||||||
check.later(obj, sig, fdecl.Body)
|
check.later(obj, info, sig, fdecl.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (check *checker) declStmt(decl ast.Decl) {
|
func (check *checker) declStmt(decl ast.Decl) {
|
||||||
@ -754,15 +801,14 @@ func (check *checker) declStmt(decl ast.Decl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case token.VAR:
|
case token.VAR:
|
||||||
// For varDecl called with a multiExpr we need the fully
|
lhs0 := make([]*Var, len(s.Names))
|
||||||
// initialized lhs. Compute it in a separate pre-pass.
|
|
||||||
lhs := make([]*Var, len(s.Names))
|
|
||||||
for i, name := range s.Names {
|
for i, name := range s.Names {
|
||||||
lhs[i] = NewVar(name.Pos(), pkg, name.Name, nil)
|
lhs0[i] = NewVar(name.Pos(), pkg, name.Name, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// declare all variables
|
// initialize all variables
|
||||||
for i, obj := range lhs {
|
for i, obj := range lhs0 {
|
||||||
|
var lhs []*Var
|
||||||
var init ast.Expr
|
var init ast.Expr
|
||||||
switch len(s.Values) {
|
switch len(s.Values) {
|
||||||
case len(s.Names):
|
case len(s.Names):
|
||||||
@ -770,20 +816,22 @@ func (check *checker) declStmt(decl ast.Decl) {
|
|||||||
init = s.Values[i]
|
init = s.Values[i]
|
||||||
case 1:
|
case 1:
|
||||||
// rhs is expected to be a multi-valued expression
|
// rhs is expected to be a multi-valued expression
|
||||||
init = &multiExpr{lhs, s.Values, nil}
|
lhs = lhs0
|
||||||
|
init = s.Values[0]
|
||||||
default:
|
default:
|
||||||
if i < len(s.Values) {
|
if i < len(s.Values) {
|
||||||
init = s.Values[i]
|
init = s.Values[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
check.varDecl(obj, lhs, s.Type, init)
|
||||||
check.varDecl(obj, s.Type, init)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
check.arityMatch(s, nil)
|
check.arityMatch(s, nil)
|
||||||
|
|
||||||
|
// declare all variables
|
||||||
|
// (only at this point are the variable scopes (parents) set)
|
||||||
for i, name := range s.Names {
|
for i, name := range s.Names {
|
||||||
check.declare(check.topScope, name, lhs[i])
|
check.declare(check.topScope, name, lhs0[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
12
go/types/testdata/init0.src
vendored
12
go/types/testdata/init0.src
vendored
@ -36,7 +36,11 @@ type S1 struct {
|
|||||||
var x3 /* ERROR initialization cycle */ S1 = S1{x3.f}
|
var x3 /* ERROR initialization cycle */ S1 = S1{x3.f}
|
||||||
|
|
||||||
// cycles via functions
|
// cycles via functions
|
||||||
var x4 = f1() // TODO(gri) recognize cycle
|
var x4 = x5
|
||||||
func f1() int {
|
var x5 /* ERROR initialization cycle */ = f1()
|
||||||
return x4
|
func f1() int { return x5*10 }
|
||||||
}
|
|
||||||
|
var x6, x7 /* ERROR initialization cycle */ = f2()
|
||||||
|
var x8 = x7
|
||||||
|
func f2() (int, int) { return f3() + f3(), 0 }
|
||||||
|
func f3() int { return x8 }
|
||||||
|
@ -89,15 +89,16 @@ func (check *checker) ident(x *operand, e *ast.Ident, def *Named, cycleOk bool)
|
|||||||
case *Var:
|
case *Var:
|
||||||
obj.used = true
|
obj.used = true
|
||||||
x.mode = variable
|
x.mode = variable
|
||||||
if obj.parent.parent == Universe && typ != Typ[Invalid] {
|
if typ != Typ[Invalid] {
|
||||||
// obj is a package-level variable, and there are no other errors
|
check.addDeclDep(obj)
|
||||||
// (such as a type-checking cycle)
|
|
||||||
check.addInitDep(obj)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case *Func:
|
case *Func:
|
||||||
obj.used = true
|
obj.used = true
|
||||||
x.mode = value
|
x.mode = value
|
||||||
|
if typ != Typ[Invalid] {
|
||||||
|
check.addDeclDep(obj)
|
||||||
|
}
|
||||||
|
|
||||||
case *Builtin:
|
case *Builtin:
|
||||||
obj.used = true // for built-ins defined by package unsafe
|
obj.used = true // for built-ins defined by package unsafe
|
||||||
|
Loading…
Reference in New Issue
Block a user