// Copyright 2013 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. // This file implements isTerminating. package types import ( "go/ast" "go/token" ) // isTerminating reports if s is a terminating statement. // If s is labeled, label is the label name; otherwise s // is "". func (check *Checker) isTerminating(s ast.Stmt, label string) bool { switch s := s.(type) { default: unreachable() case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.SendStmt, *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, *ast.DeferStmt, *ast.RangeStmt: // no chance case *ast.LabeledStmt: return check.isTerminating(s.Stmt, s.Label.Name) case *ast.ExprStmt: // calling the predeclared (possibly parenthesized) panic() function is terminating if call, ok := unparen(s.X).(*ast.CallExpr); ok && check.isPanic[call] { return true } case *ast.ReturnStmt: return true case *ast.BranchStmt: if s.Tok == token.GOTO || s.Tok == token.FALLTHROUGH { return true } case *ast.BlockStmt: return check.isTerminatingList(s.List, "") case *ast.IfStmt: if s.Else != nil && check.isTerminating(s.Body, "") && check.isTerminating(s.Else, "") { return true } case *ast.SwitchStmt: return check.isTerminatingSwitch(s.Body, label) case *ast.TypeSwitchStmt: return check.isTerminatingSwitch(s.Body, label) case *ast.SelectStmt: for _, s := range s.Body.List { cc := s.(*ast.CommClause) if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { return false } } return true case *ast.ForStmt: if s.Cond == nil && !hasBreak(s.Body, label, true) { return true } } return false } func (check *Checker) isTerminatingList(list []ast.Stmt, label string) bool { // trailing empty statements are permitted - skip them for i := len(list) - 1; i >= 0; i-- { if _, ok := list[i].(*ast.EmptyStmt); !ok { return check.isTerminating(list[i], label) } } return false // all statements are empty } func (check *Checker) isTerminatingSwitch(body *ast.BlockStmt, label string) bool { hasDefault := false for _, s := range body.List { cc := s.(*ast.CaseClause) if cc.List == nil { hasDefault = true } if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { return false } } return hasDefault } // TODO(gri) For nested breakable statements, the current implementation of hasBreak // will traverse the same subtree repeatedly, once for each label. Replace // with a single-pass label/break matching phase. // hasBreak reports if s is or contains a break statement // referring to the label-ed statement or implicit-ly the // closest outer breakable statement. func hasBreak(s ast.Stmt, label string, implicit bool) bool { switch s := s.(type) { default: unreachable() case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.ExprStmt, *ast.SendStmt, *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, *ast.DeferStmt, *ast.ReturnStmt: // no chance case *ast.LabeledStmt: return hasBreak(s.Stmt, label, implicit) case *ast.BranchStmt: if s.Tok == token.BREAK { if s.Label == nil { return implicit } if s.Label.Name == label { return true } } case *ast.BlockStmt: return hasBreakList(s.List, label, implicit) case *ast.IfStmt: if hasBreak(s.Body, label, implicit) || s.Else != nil && hasBreak(s.Else, label, implicit) { return true } case *ast.CaseClause: return hasBreakList(s.Body, label, implicit) case *ast.SwitchStmt: if label != "" && hasBreak(s.Body, label, false) { return true } case *ast.TypeSwitchStmt: if label != "" && hasBreak(s.Body, label, false) { return true } case *ast.CommClause: return hasBreakList(s.Body, label, implicit) case *ast.SelectStmt: if label != "" && hasBreak(s.Body, label, false) { return true } case *ast.ForStmt: if label != "" && hasBreak(s.Body, label, false) { return true } case *ast.RangeStmt: if label != "" && hasBreak(s.Body, label, false) { return true } } return false } func hasBreakList(list []ast.Stmt, label string, implicit bool) bool { for _, s := range list { if hasBreak(s, label, implicit) { return true } } return false }