| // 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 |
| } |