go.tools/go/types: check unlabeled break, continue statements

This CL temporarily removes some preliminary label checks.
They will be implemented completely in a subsequent CL.

R=adonovan
CC=golang-dev
https://golang.org/cl/14055043
This commit is contained in:
Robert Griesemer 2013-09-27 13:43:11 -07:00
parent 27b698bc2a
commit 0730d79f0f
5 changed files with 228 additions and 67 deletions

View File

@ -49,7 +49,7 @@ type checker struct {
// 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
funcSig *Signature // signature of currently type-checked function funcSig *Signature // signature of currently type-checked function
labels *Scope // label scope of currently type-checked function; lazily allocated hasLabel bool // set if a function makes use of labels (only ~1% of functions)
// debugging // debugging
indent int // indentation for tracing indent int // indentation for tracing

View File

@ -396,21 +396,16 @@ 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.labels = nil // lazily allocated check.hasLabel = false
check.stmtList(f.body.List, false) check.stmtList(0, f.body.List)
if check.hasLabel {
// TODO(gri) check label use
}
if f.sig.results.Len() > 0 && !check.isTerminating(f.body, "") { if f.sig.results.Len() > 0 && !check.isTerminating(f.body, "") {
check.errorf(f.body.Rbrace, "missing return") check.errorf(f.body.Rbrace, "missing return")
} }
// spec: "It is illegal to define a label that is never used."
if check.labels != nil {
for _, obj := range check.labels.elems {
if l := obj.(*Label); !l.used {
check.errorf(l.pos, "%s defined but not used", l.name)
}
}
}
} }
// Phase 5: Check for declared but not used packages and variables. // Phase 5: Check for declared but not used packages and variables.

View File

@ -117,8 +117,8 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
func TestStdtest(t *testing.T) { func TestStdtest(t *testing.T) {
testTestDir(t, filepath.Join(runtime.GOROOT(), "test"), testTestDir(t, filepath.Join(runtime.GOROOT(), "test"),
"cmplxdivide.go", // also needs file cmplxdivide1.go - ignore "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
"goto.go", "label1.go", // TODO(gri) implement missing label checks "goto.go", "label.go", "label1.go", // TODO(gri) implement missing label checks
"mapnan.go", "sigchld.go", // don't work on Windows; testTestDir should consult build tags "mapnan.go", "sigchld.go", // don't work on Windows; testTestDir should consult build tags
"sizeof.go", "switch.go", // TODO(gri) tone down duplicate checking in expr switches "sizeof.go", "switch.go", // TODO(gri) tone down duplicate checking in expr switches
"typeswitch2.go", // TODO(gri) implement duplicate checking in type switches "typeswitch2.go", // TODO(gri) implement duplicate checking in type switches

View File

@ -11,20 +11,32 @@ import (
"go/token" "go/token"
) )
func (check *checker) optionalStmt(s ast.Stmt) { // stmtContext is a bitset describing the environment
// (outer statements) containing a statement.
type stmtContext uint
const (
fallthroughOk stmtContext = 1 << iota
inBreakable
inContinuable
)
func (check *checker) initStmt(s ast.Stmt) {
if s != nil { if s != nil {
scope := check.topScope check.stmt(0, s)
check.stmt(s, false)
assert(check.topScope == scope)
} }
} }
func (check *checker) stmtList(list []ast.Stmt, fallthroughOk bool) { func (check *checker) stmtList(ctxt stmtContext, list []ast.Stmt) {
scope := check.topScope ok := ctxt&fallthroughOk != 0
inner := ctxt &^ fallthroughOk
for i, s := range list { for i, s := range list {
check.stmt(s, fallthroughOk && i+1 == len(list)) inner := inner
if ok && i+1 == len(list) {
inner |= fallthroughOk
}
check.stmt(inner, s)
} }
assert(check.topScope == scope)
} }
func (check *checker) multipleDefaults(list []ast.Stmt) { func (check *checker) multipleDefaults(list []ast.Stmt) {
@ -53,10 +65,10 @@ func (check *checker) multipleDefaults(list []ast.Stmt) {
} }
} }
func (check *checker) openScope(node ast.Node) { func (check *checker) openScope(s ast.Stmt) {
s := NewScope(check.topScope) scope := NewScope(check.topScope)
check.recordScope(node, s) check.recordScope(s, scope)
check.topScope = s check.topScope = scope
} }
func (check *checker) closeScope() { func (check *checker) closeScope() {
@ -88,11 +100,23 @@ func (check *checker) suspendedCall(keyword string, call *ast.CallExpr) {
} }
// stmt typechecks statement s. // stmt typechecks statement s.
func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) { func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
// statements cannot use iota in general // statements cannot use iota in general
// (constant declarations set it explicitly) // (constant declarations set it explicitly)
assert(check.iota == nil) assert(check.iota == nil)
// statements must end with the same top scope as they started with
if debug {
defer func(scope *Scope) {
// don't check if code is panicking
if p := recover(); p != nil {
panic(p)
}
assert(scope == check.topScope)
}(check.topScope)
}
inner := ctxt &^ fallthroughOk
switch s := s.(type) { switch s := s.(type) {
case *ast.BadStmt, *ast.EmptyStmt: case *ast.BadStmt, *ast.EmptyStmt:
// ignore // ignore
@ -101,18 +125,8 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
check.declStmt(s.Decl) check.declStmt(s.Decl)
case *ast.LabeledStmt: case *ast.LabeledStmt:
scope := check.labels check.hasLabel = true
if scope == nil { check.stmt(ctxt, s.Stmt)
scope = NewScope(nil) // no label scope chain
check.labels = scope
}
label := s.Label
l := NewLabel(label.Pos(), label.Name)
// Labels are not resolved yet - mark them as used to avoid errors.
// TODO(gri) fix this
l.used = true
check.declareObj(scope, label, l)
check.stmt(s.Stmt, fallthroughOk)
case *ast.ExprStmt: case *ast.ExprStmt:
// spec: "With the exception of specific built-in functions, // spec: "With the exception of specific built-in functions,
@ -227,19 +241,23 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
} }
case *ast.BranchStmt: case *ast.BranchStmt:
if s.Label != nil {
check.hasLabel = true
return // checks handled in separate pass
}
switch s.Tok { switch s.Tok {
case token.BREAK: case token.BREAK:
// TODO(gri) implement checks if ctxt&inBreakable == 0 {
case token.CONTINUE: check.errorf(s.Pos(), "break not in for, switch, or select statement")
// TODO(gri) implement checks
case token.GOTO:
// TODO(gri) implement checks
case token.FALLTHROUGH:
if s.Label != nil {
check.invalidAST(s.Label.Pos(), "fallthrough statement cannot have label")
// ok to continue
} }
if !fallthroughOk { case token.CONTINUE:
if ctxt&inContinuable == 0 {
check.errorf(s.Pos(), "continue not in for statement")
}
case token.GOTO:
check.invalidAST(s.Pos(), "goto without label")
case token.FALLTHROUGH:
if ctxt&fallthroughOk == 0 {
check.errorf(s.Pos(), "fallthrough statement out of place") check.errorf(s.Pos(), "fallthrough statement out of place")
} }
default: default:
@ -248,24 +266,27 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
case *ast.BlockStmt: case *ast.BlockStmt:
check.openScope(s) check.openScope(s)
check.stmtList(s.List, false) check.stmtList(inner, s.List)
check.closeScope() check.closeScope()
case *ast.IfStmt: case *ast.IfStmt:
check.openScope(s) check.openScope(s)
check.optionalStmt(s.Init) check.initStmt(s.Init)
var x operand var x operand
check.expr(&x, s.Cond) check.expr(&x, s.Cond)
if x.mode != invalid && !isBoolean(x.typ) { if x.mode != invalid && !isBoolean(x.typ) {
check.errorf(s.Cond.Pos(), "non-boolean condition in if statement") check.errorf(s.Cond.Pos(), "non-boolean condition in if statement")
} }
check.stmt(s.Body, false) check.stmt(inner, s.Body)
check.optionalStmt(s.Else) if s.Else != nil {
check.stmt(inner, s.Else)
}
check.closeScope() check.closeScope()
case *ast.SwitchStmt: case *ast.SwitchStmt:
inner |= inBreakable
check.openScope(s) check.openScope(s)
check.optionalStmt(s.Init) check.initStmt(s.Init)
var x operand var x operand
tag := s.Tag tag := s.Tag
if tag == nil { if tag == nil {
@ -326,15 +347,20 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
} }
} }
check.openScope(clause) check.openScope(clause)
check.stmtList(clause.Body, i+1 < len(s.Body.List)) inner := inner
if i+1 < len(s.Body.List) {
inner |= fallthroughOk
}
check.stmtList(inner, clause.Body)
check.closeScope() check.closeScope()
} }
check.closeScope() check.closeScope()
case *ast.TypeSwitchStmt: case *ast.TypeSwitchStmt:
inner |= inBreakable
check.openScope(s) check.openScope(s)
defer check.closeScope() defer check.closeScope()
check.optionalStmt(s.Init) check.initStmt(s.Init)
// A type switch guard must be of the form: // A type switch guard must be of the form:
// //
@ -420,7 +446,7 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
check.declareObj(check.topScope, nil, obj) check.declareObj(check.topScope, nil, obj)
check.recordImplicit(clause, obj) check.recordImplicit(clause, obj)
} }
check.stmtList(clause.Body, false) check.stmtList(inner, clause.Body)
check.closeScope() check.closeScope()
} }
// If a lhs variable was declared but there were no case clauses, make sure // If a lhs variable was declared but there were no case clauses, make sure
@ -434,6 +460,7 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
} }
case *ast.SelectStmt: case *ast.SelectStmt:
inner |= inBreakable
check.multipleDefaults(s.Body.List) check.multipleDefaults(s.Body.List)
for _, s := range s.Body.List { for _, s := range s.Body.List {
clause, _ := s.(*ast.CommClause) clause, _ := s.(*ast.CommClause)
@ -441,14 +468,17 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
continue // error reported before continue // error reported before
} }
check.openScope(clause) check.openScope(clause)
check.optionalStmt(clause.Comm) // TODO(gri) check correctness of c.Comm (must be Send/RecvStmt) if s := clause.Comm; s != nil {
check.stmtList(clause.Body, false) check.stmt(inner, s) // TODO(gri) check correctness of c.Comm (must be Send/RecvStmt)
}
check.stmtList(inner, clause.Body)
check.closeScope() check.closeScope()
} }
case *ast.ForStmt: case *ast.ForStmt:
inner |= inBreakable | inContinuable
check.openScope(s) check.openScope(s)
check.optionalStmt(s.Init) check.initStmt(s.Init)
if s.Cond != nil { if s.Cond != nil {
var x operand var x operand
check.expr(&x, s.Cond) check.expr(&x, s.Cond)
@ -456,11 +486,12 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
check.errorf(s.Cond.Pos(), "non-boolean condition in for statement") check.errorf(s.Cond.Pos(), "non-boolean condition in for statement")
} }
} }
check.optionalStmt(s.Post) check.initStmt(s.Post)
check.stmt(s.Body, false) check.stmt(inner, s.Body)
check.closeScope() check.closeScope()
case *ast.RangeStmt: case *ast.RangeStmt:
inner |= inBreakable | inContinuable
check.openScope(s) check.openScope(s)
defer check.closeScope() defer check.closeScope()
@ -472,7 +503,7 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
// if we don't have a declaration, we can still check the loop's body // if we don't have a declaration, we can still check the loop's body
// (otherwise we can't because we are missing the declared variables) // (otherwise we can't because we are missing the declared variables)
if !decl { if !decl {
check.stmt(s.Body, false) check.stmt(inner, s.Body)
} }
return return
} }
@ -516,7 +547,7 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
check.errorf(x.pos(), "cannot range over %s", &x) check.errorf(x.pos(), "cannot range over %s", &x)
// if we don't have a declaration, we can still check the loop's body // if we don't have a declaration, we can still check the loop's body
if !decl { if !decl {
check.stmt(s.Body, false) check.stmt(inner, s.Body)
} }
return return
} }
@ -578,7 +609,7 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
} }
} }
check.stmt(s.Body, false) check.stmt(inner, s.Body)
default: default:
check.errorf(s.Pos(), "invalid statement") check.errorf(s.Pos(), "invalid statement")

View File

@ -180,6 +180,133 @@ func defers() {
defer len /* ERROR "defer discards result" */ (c) defer len /* ERROR "defer discards result" */ (c)
} }
func breaks() {
var x, y int
break /* ERROR "break" */
{
break /* ERROR "break" */
}
if x < y {
break /* ERROR "break" */
}
switch x {
case 0:
break
case 1:
if x == y {
break
}
default:
break
break
}
var z interface{}
switch z.(type) {
case int:
break
}
for {
break
}
var a []int
for _ = range a {
break
}
for {
if x == y {
break
}
}
var ch chan int
select {
case <-ch:
break
}
select {
case <-ch:
if x == y {
break
}
default:
break
}
}
func continues() {
var x, y int
continue /* ERROR "continue" */
{
continue /* ERROR "continue" */
}
if x < y {
continue /* ERROR "continue" */
}
switch x {
case 0:
continue /* ERROR "continue" */
}
var z interface{}
switch z.(type) {
case int:
continue /* ERROR "continue" */
}
var ch chan int
select {
case <-ch:
continue /* ERROR "continue" */
}
for i := 0; i < 10; i++ {
continue
if x < y {
continue
break
}
switch x {
case y:
continue
default:
break
}
select {
case <-ch:
continue
}
}
var a []int
for _ = range a {
continue
if x < y {
continue
break
}
switch x {
case y:
continue
default:
break
}
select {
case <-ch:
continue
}
}
}
func switches0() { func switches0() {
var x int var x int
@ -263,6 +390,14 @@ func switches1() {
default: default:
L6: L7: L8: fallthrough /* ERROR "fallthrough statement out of place" */ L6: L7: L8: fallthrough /* ERROR "fallthrough statement out of place" */
} }
switch x {
case 0:
{
fallthrough /* ERROR "fallthrough statement out of place" */
}
default:
}
} }
type I interface { type I interface {
@ -472,10 +607,10 @@ func rangeloops() {
func labels0() { func labels0() {
L0: L0:
L1: L1:
L1 /* ERROR "redeclared" */: L1:
if true { if true {
L2: L2:
L0 /* ERROR "redeclared" */: L0:
} }
_ = func() { _ = func() {
L0: L0: