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/gotos.src"},
{"testdata/labels.src"},
{"testdata/issues.src"},
}
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:
if sig, ok := check.typ(e.Type, nil, false).(*Signature); ok {
x.mode = value
x.typ = sig
// Anonymous functions are considered part of the
// init expression/func declaration which contains
// them: use the current package-level declaration
// info.
check.later(nil, check.decl, sig, e.Body)
// them: use existing package-level declaration info.
check.funcBody("", sig, e.Body)
x.mode = value
x.typ = sig
} else {
check.invalidAST(e.Pos(), "invalid function literal %s", e)
goto Error

View File

@ -49,20 +49,12 @@ type declInfo struct {
}
type funcInfo struct {
obj *Func // for debugging/tracing only
name string // for tracing only
info *declInfo // for cycle detection
sig *Signature
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
// have the appropriate number of names and init exprs. For const
// 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.
// Note: funcList may grow while iterating through it - cannot use range clause.
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
for _, f := range check.funcList {
check.decl = f.info
check.stmtList(0, f.body.List)
if check.hasLabel {
check.labels(f.body)
}
if f.sig.results.Len() > 0 && !check.isTerminating(f.body, "") {
check.errorf(f.body.Rbrace, "missing return")
}
check.funcBody(f.name, f.sig, f.body)
}
// Phase 5: Check initialization dependencies.
@ -455,16 +422,19 @@ func (check *checker) resolveFiles(files []*ast.File) {
objList = nil // not needed anymore
check.initMap = nil // not needed anymore
// Phase 6: Check for declared but not used packages and variables.
// Note: must happen after checking all functions because closures may affect outer scopes
// Phase 6: Check for declared but not used packages and function variables.
// 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
// any of its exported identifiers. To import a package solely for its side-effects
// (initialization), use the blank identifier as explicit package name."
// We must skip this check if we didn't look at function bodies.
if !check.conf.IgnoreFuncBodies {
for i, scope := range fileScopes {
var usedDotImports map[*Package]bool // lazily allocated
for _, obj := range scope.elems {
@ -495,11 +465,11 @@ func (check *checker) resolveFiles(files []*ast.File) {
}
}
}
}
// 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
// them before the general analysis.
// Note: This check could be done on a per-function basis.
for _, vars := range check.lhsVarsList {
// len(vars) > 0
var used bool
@ -517,6 +487,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
// spec: "Implementation restriction: A compiler may make it illegal to
// 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 {
check.usage(f.sig.scope)
}
@ -839,7 +810,11 @@ func (check *checker) funcDecl(obj *Func, info *declInfo) {
}
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) {

View File

@ -7,10 +7,45 @@
package types
import (
"fmt"
"go/ast"
"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
// (outer statements) containing a statement.
type stmtContext uint

View File

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