| // 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 types2 |
| |
| import ( |
| "cmd/compile/internal/syntax" |
| ) |
| |
| // 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 syntax.Stmt, label string) bool { |
| switch s := s.(type) { |
| default: |
| unreachable() |
| |
| case *syntax.DeclStmt, *syntax.EmptyStmt, *syntax.SendStmt, |
| *syntax.AssignStmt, *syntax.CallStmt: |
| // no chance |
| |
| case *syntax.LabeledStmt: |
| return check.isTerminating(s.Stmt, s.Label.Value) |
| |
| case *syntax.ExprStmt: |
| // calling the predeclared (possibly parenthesized) panic() function is terminating |
| if call, ok := unparen(s.X).(*syntax.CallExpr); ok && check.isPanic[call] { |
| return true |
| } |
| |
| case *syntax.ReturnStmt: |
| return true |
| |
| case *syntax.BranchStmt: |
| if s.Tok == syntax.Goto || s.Tok == syntax.Fallthrough { |
| return true |
| } |
| |
| case *syntax.BlockStmt: |
| return check.isTerminatingList(s.List, "") |
| |
| case *syntax.IfStmt: |
| if s.Else != nil && |
| check.isTerminating(s.Then, "") && |
| check.isTerminating(s.Else, "") { |
| return true |
| } |
| |
| case *syntax.SwitchStmt: |
| return check.isTerminatingSwitch(s.Body, label) |
| |
| case *syntax.SelectStmt: |
| for _, cc := range s.Body { |
| if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { |
| return false |
| } |
| |
| } |
| return true |
| |
| case *syntax.ForStmt: |
| if s.Cond == nil && !hasBreak(s.Body, label, true) { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| func (check *Checker) isTerminatingList(list []syntax.Stmt, label string) bool { |
| // trailing empty statements are permitted - skip them |
| for i := len(list) - 1; i >= 0; i-- { |
| if _, ok := list[i].(*syntax.EmptyStmt); !ok { |
| return check.isTerminating(list[i], label) |
| } |
| } |
| return false // all statements are empty |
| } |
| |
| func (check *Checker) isTerminatingSwitch(body []*syntax.CaseClause, label string) bool { |
| hasDefault := false |
| for _, cc := range body { |
| if cc.Cases == 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 syntax.Stmt, label string, implicit bool) bool { |
| switch s := s.(type) { |
| default: |
| unreachable() |
| |
| case *syntax.DeclStmt, *syntax.EmptyStmt, *syntax.ExprStmt, |
| *syntax.SendStmt, *syntax.AssignStmt, *syntax.CallStmt, |
| *syntax.ReturnStmt: |
| // no chance |
| |
| case *syntax.LabeledStmt: |
| return hasBreak(s.Stmt, label, implicit) |
| |
| case *syntax.BranchStmt: |
| if s.Tok == syntax.Break { |
| if s.Label == nil { |
| return implicit |
| } |
| if s.Label.Value == label { |
| return true |
| } |
| } |
| |
| case *syntax.BlockStmt: |
| return hasBreakList(s.List, label, implicit) |
| |
| case *syntax.IfStmt: |
| if hasBreak(s.Then, label, implicit) || |
| s.Else != nil && hasBreak(s.Else, label, implicit) { |
| return true |
| } |
| |
| case *syntax.SwitchStmt: |
| if label != "" && hasBreakCaseList(s.Body, label, false) { |
| return true |
| } |
| |
| case *syntax.SelectStmt: |
| if label != "" && hasBreakCommList(s.Body, label, false) { |
| return true |
| } |
| |
| case *syntax.ForStmt: |
| if label != "" && hasBreak(s.Body, label, false) { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| func hasBreakList(list []syntax.Stmt, label string, implicit bool) bool { |
| for _, s := range list { |
| if hasBreak(s, label, implicit) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func hasBreakCaseList(list []*syntax.CaseClause, label string, implicit bool) bool { |
| for _, s := range list { |
| if hasBreakList(s.Body, label, implicit) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func hasBreakCommList(list []*syntax.CommClause, label string, implicit bool) bool { |
| for _, s := range list { |
| if hasBreakList(s.Body, label, implicit) { |
| return true |
| } |
| } |
| return false |
| } |