go.tools/go/types: use correct outer scope state for inner functions

Inner function bodies must be type-checked "in-place" for
them to see the correct state of the surrounding scopes.

Fixes golang/go#7035.

R=adonovan
CC=golang-codereviews
https://golang.org/cl/49350043
This commit is contained in:
Robert Griesemer 2014-01-09 08:40:32 -08:00
parent d0b88d2206
commit 4e620158a2
7 changed files with 109 additions and 76 deletions

View File

@ -77,6 +77,7 @@ var tests = [][]string{
{"testdata/stmt1.src"}, {"testdata/stmt1.src"},
{"testdata/gotos.src"}, {"testdata/gotos.src"},
{"testdata/labels.src"}, {"testdata/labels.src"},
{"testdata/issues.src"},
} }
var fset = token.NewFileSet() var fset = token.NewFileSet()

View File

@ -967,13 +967,12 @@ func (check *checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
case *ast.FuncLit: case *ast.FuncLit:
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.typ = sig
// Anonymous functions are considered part of the // Anonymous functions are considered part of the
// init expression/func declaration which contains // init expression/func declaration which contains
// them: use the current package-level declaration // them: use existing package-level declaration info.
// info. check.funcBody("", sig, e.Body)
check.later(nil, check.decl, sig, e.Body) x.mode = value
x.typ = sig
} else { } else {
check.invalidAST(e.Pos(), "invalid function literal %s", e) check.invalidAST(e.Pos(), "invalid function literal %s", e)
goto Error goto Error

View File

@ -49,20 +49,12 @@ type declInfo struct {
} }
type funcInfo struct { type funcInfo struct {
obj *Func // for debugging/tracing only name string // for tracing only
info *declInfo // for cycle detection 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.
func (check *checker) later(f *Func, info *declInfo, sig *Signature, body *ast.BlockStmt) {
// functions implemented elsewhere (say in assembly) have no body
if !check.conf.IgnoreFuncBodies && body != nil {
check.funcList = append(check.funcList, funcInfo{f, info, sig, body})
}
}
// arityMatch checks that the lhs and rhs of a const or var decl // arityMatch checks that the lhs and rhs of a const or var decl
// have the appropriate number of names and init exprs. For const // have the appropriate number of names and init exprs. For const
// decls, init is the value spec providing the init exprs; for // decls, init is the value spec providing the init exprs; for
@ -408,34 +400,9 @@ func (check *checker) resolveFiles(files []*ast.File) {
// Phase 4: Typecheck all functions bodies. // Phase 4: Typecheck all functions bodies.
// Note: funcList may grow while iterating through it - cannot use range clause. for _, f := range check.funcList {
for i := 0; i < len(check.funcList); i++ {
// TODO(gri) Factor out this code into a dedicated function
// with its own context so that it can be run concurrently
// eventually.
f := check.funcList[i]
if trace {
s := "<function literal>"
if f.obj != nil {
s = f.obj.name
}
fmt.Println("---", s)
}
check.topScope = f.sig.scope // open function scope
check.funcSig = f.sig
check.hasLabel = false
check.decl = f.info check.decl = f.info
check.stmtList(0, f.body.List) check.funcBody(f.name, f.sig, f.body)
if check.hasLabel {
check.labels(f.body)
}
if f.sig.results.Len() > 0 && !check.isTerminating(f.body, "") {
check.errorf(f.body.Rbrace, "missing return")
}
} }
// Phase 5: Check initialization dependencies. // Phase 5: Check initialization dependencies.
@ -455,44 +422,46 @@ func (check *checker) resolveFiles(files []*ast.File) {
objList = nil // not needed anymore objList = nil // not needed anymore
check.initMap = 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 function variables.
// Note: must happen after checking all functions because closures may affect outer scopes // Note: must happen after checking all functions because function bodies
// may introduce package uses
// If function bodies are not checked, packages' uses are likely missing,
// and there are no unused function variables. Nothing left to do.
if check.conf.IgnoreFuncBodies {
return
}
// spec: "It is illegal (...) to directly import a package without referring to // spec: "It is illegal (...) to directly import a package without referring to
// any of its exported identifiers. To import a package solely for its side-effects // any of its exported identifiers. To import a package solely for its side-effects
// (initialization), use the blank identifier as explicit package name." // (initialization), use the blank identifier as explicit package name."
for i, scope := range fileScopes {
// We must skip this check if we didn't look at function bodies. var usedDotImports map[*Package]bool // lazily allocated
for _, obj := range scope.elems {
if !check.conf.IgnoreFuncBodies { switch obj := obj.(type) {
for i, scope := range fileScopes { case *PkgName:
var usedDotImports map[*Package]bool // lazily allocated // Unused "blank imports" are automatically ignored
for _, obj := range scope.elems { // since _ identifiers are not entered into scopes.
switch obj := obj.(type) { if !obj.used {
case *PkgName: check.errorf(obj.pos, "%q imported but not used", obj.pkg.path)
// Unused "blank imports" are automatically ignored }
// since _ identifiers are not entered into scopes. default:
if !obj.used { // All other objects in the file scope must be dot-
check.errorf(obj.pos, "%q imported but not used", obj.pkg.path) // imported. If an object was used, mark its package
} // as used.
default: if obj.isUsed() {
// All other objects in the file scope must be dot- if usedDotImports == nil {
// imported. If an object was used, mark its package usedDotImports = make(map[*Package]bool)
// as used.
if obj.isUsed() {
if usedDotImports == nil {
usedDotImports = make(map[*Package]bool)
}
usedDotImports[obj.Pkg()] = true
} }
usedDotImports[obj.Pkg()] = true
} }
} }
// Iterate through all dot-imports for this file and }
// check if the corresponding package was used. // Iterate through all dot-imports for this file and
for pkg, pos := range dotImports[i] { // check if the corresponding package was used.
if !usedDotImports[pkg] { for pkg, pos := range dotImports[i] {
check.errorf(pos, "%q imported but not used", pkg.path) if !usedDotImports[pkg] {
} check.errorf(pos, "%q imported but not used", pkg.path)
} }
} }
} }
@ -500,6 +469,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
// Each set of implicitly declared lhs variables in a type switch acts collectively // Each set of implicitly declared lhs variables in a type switch acts collectively
// as a single lhs variable. If any one was 'used', all of them are 'used'. Handle // as a single lhs variable. If any one was 'used', all of them are 'used'. Handle
// them before the general analysis. // them before the general analysis.
// Note: This check could be done on a per-function basis.
for _, vars := range check.lhsVarsList { for _, vars := range check.lhsVarsList {
// len(vars) > 0 // len(vars) > 0
var used bool var used bool
@ -517,6 +487,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
// spec: "Implementation restriction: A compiler may make it illegal to // spec: "Implementation restriction: A compiler may make it illegal to
// declare a variable inside a function body if the variable is never used." // declare a variable inside a function body if the variable is never used."
// Note: Inner functions are handled via the child scopes of the enclosing function.
for _, f := range check.funcList { for _, f := range check.funcList {
check.usage(f.sig.scope) check.usage(f.sig.scope)
} }
@ -839,7 +810,11 @@ func (check *checker) funcDecl(obj *Func, info *declInfo) {
} }
obj.typ = sig obj.typ = sig
check.later(obj, info, sig, fdecl.Body) // function body must be type-checked after global declarations
// (functions implemented elsewhere have no body)
if !check.conf.IgnoreFuncBodies && fdecl.Body != nil {
check.funcList = append(check.funcList, funcInfo{obj.name, info, sig, fdecl.Body})
}
} }
func (check *checker) declStmt(decl ast.Decl) { func (check *checker) declStmt(decl ast.Decl) {

View File

@ -7,10 +7,45 @@
package types package types
import ( import (
"fmt"
"go/ast" "go/ast"
"go/token" "go/token"
) )
func (check *checker) funcBody(name string, sig *Signature, body *ast.BlockStmt) {
if trace {
if name == "" {
name = "<function literal>"
}
fmt.Printf("--- %s: %s {\n", name, sig)
defer fmt.Println("--- <end>")
}
// save/restore outer function state
defer func(scope *Scope, sig *Signature, label bool, indent int) {
check.topScope = scope
check.funcSig = sig
check.hasLabel = label
check.indent = indent
}(check.topScope, check.funcSig, check.hasLabel, check.indent)
// setup inner function state
check.topScope = sig.scope
check.funcSig = sig
check.hasLabel = false
check.indent = 0
check.stmtList(0, body.List)
if check.hasLabel {
check.labels(body)
}
if sig.results.Len() > 0 && !check.isTerminating(body, "") {
check.errorf(body.Rbrace, "missing return")
}
}
// stmtContext is a bitset describing the environment // stmtContext is a bitset describing the environment
// (outer statements) containing a statement. // (outer statements) containing a statement.
type stmtContext uint type stmtContext uint

View File

@ -27,7 +27,8 @@ import (
) )
import "fmt" import "fmt"
import f "fmt" import f1 "fmt"
import f2 "fmt"
// reflect.flag must not be visible in this package // reflect.flag must not be visible in this package
type flag int type flag int
@ -39,5 +40,11 @@ type Value /* ERROR "already declared in this file" */ struct{}
var _ = fmt.Println // use "fmt" var _ = fmt.Println // use "fmt"
func _() { func _() {
f.Println() // use "fmt" f1.Println() // use "fmt"
}
func _() {
_ = func() {
f2.Println() // use "fmt"
}
} }

View File

@ -53,7 +53,7 @@ func f3() int { return x8 }
// cycles via closures // cycles via closures
var x9 /* ERROR initialization cycle */ = func() int { return x9 }() var x9 /* ERROR illegal cycle */ = func() int { return x9 }()
var x10 /* ERROR initialization cycle */ = f4() var x10 /* ERROR initialization cycle */ = f4()

16
go/types/testdata/issues.src vendored Normal file
View File

@ -0,0 +1,16 @@
// Copyright 2014 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 issues
import "fmt"
func issue7035() {
type T struct{ X int }
_ = func() {
fmt.Println() // must refer to imported fmt rather than the fmt below
}
fmt := new(T)
_ = fmt.X
}