github.com/hlts2/go@v0.0.0-20170904000733-812b34efaed8/src/go/types/return.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // This file implements isTerminating.
     6  
     7  package types
     8  
     9  import (
    10  	"go/ast"
    11  	"go/token"
    12  )
    13  
    14  // isTerminating reports if s is a terminating statement.
    15  // If s is labeled, label is the label name; otherwise s
    16  // is "".
    17  func (check *Checker) isTerminating(s ast.Stmt, label string) bool {
    18  	switch s := s.(type) {
    19  	default:
    20  		unreachable()
    21  
    22  	case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.SendStmt,
    23  		*ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, *ast.DeferStmt,
    24  		*ast.RangeStmt:
    25  		// no chance
    26  
    27  	case *ast.LabeledStmt:
    28  		return check.isTerminating(s.Stmt, s.Label.Name)
    29  
    30  	case *ast.ExprStmt:
    31  		// the predeclared (possibly parenthesized) panic() function is terminating
    32  		if call, _ := unparen(s.X).(*ast.CallExpr); call != nil {
    33  			if id, _ := call.Fun.(*ast.Ident); id != nil {
    34  				if _, obj := check.scope.LookupParent(id.Name, token.NoPos); obj != nil {
    35  					if b, _ := obj.(*Builtin); b != nil && b.id == _Panic {
    36  						return true
    37  					}
    38  				}
    39  			}
    40  		}
    41  
    42  	case *ast.ReturnStmt:
    43  		return true
    44  
    45  	case *ast.BranchStmt:
    46  		if s.Tok == token.GOTO || s.Tok == token.FALLTHROUGH {
    47  			return true
    48  		}
    49  
    50  	case *ast.BlockStmt:
    51  		return check.isTerminatingList(s.List, "")
    52  
    53  	case *ast.IfStmt:
    54  		if s.Else != nil &&
    55  			check.isTerminating(s.Body, "") &&
    56  			check.isTerminating(s.Else, "") {
    57  			return true
    58  		}
    59  
    60  	case *ast.SwitchStmt:
    61  		return check.isTerminatingSwitch(s.Body, label)
    62  
    63  	case *ast.TypeSwitchStmt:
    64  		return check.isTerminatingSwitch(s.Body, label)
    65  
    66  	case *ast.SelectStmt:
    67  		for _, s := range s.Body.List {
    68  			cc := s.(*ast.CommClause)
    69  			if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) {
    70  				return false
    71  			}
    72  
    73  		}
    74  		return true
    75  
    76  	case *ast.ForStmt:
    77  		if s.Cond == nil && !hasBreak(s.Body, label, true) {
    78  			return true
    79  		}
    80  	}
    81  
    82  	return false
    83  }
    84  
    85  func (check *Checker) isTerminatingList(list []ast.Stmt, label string) bool {
    86  	// trailing empty statements are permitted - skip them
    87  	for i := len(list) - 1; i >= 0; i-- {
    88  		if _, ok := list[i].(*ast.EmptyStmt); !ok {
    89  			return check.isTerminating(list[i], label)
    90  		}
    91  	}
    92  	return false // all statements are empty
    93  }
    94  
    95  func (check *Checker) isTerminatingSwitch(body *ast.BlockStmt, label string) bool {
    96  	hasDefault := false
    97  	for _, s := range body.List {
    98  		cc := s.(*ast.CaseClause)
    99  		if cc.List == nil {
   100  			hasDefault = true
   101  		}
   102  		if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) {
   103  			return false
   104  		}
   105  	}
   106  	return hasDefault
   107  }
   108  
   109  // TODO(gri) For nested breakable statements, the current implementation of hasBreak
   110  //	     will traverse the same subtree repeatedly, once for each label. Replace
   111  //           with a single-pass label/break matching phase.
   112  
   113  // hasBreak reports if s is or contains a break statement
   114  // referring to the label-ed statement or implicit-ly the
   115  // closest outer breakable statement.
   116  func hasBreak(s ast.Stmt, label string, implicit bool) bool {
   117  	switch s := s.(type) {
   118  	default:
   119  		unreachable()
   120  
   121  	case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.ExprStmt,
   122  		*ast.SendStmt, *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt,
   123  		*ast.DeferStmt, *ast.ReturnStmt:
   124  		// no chance
   125  
   126  	case *ast.LabeledStmt:
   127  		return hasBreak(s.Stmt, label, implicit)
   128  
   129  	case *ast.BranchStmt:
   130  		if s.Tok == token.BREAK {
   131  			if s.Label == nil {
   132  				return implicit
   133  			}
   134  			if s.Label.Name == label {
   135  				return true
   136  			}
   137  		}
   138  
   139  	case *ast.BlockStmt:
   140  		return hasBreakList(s.List, label, implicit)
   141  
   142  	case *ast.IfStmt:
   143  		if hasBreak(s.Body, label, implicit) ||
   144  			s.Else != nil && hasBreak(s.Else, label, implicit) {
   145  			return true
   146  		}
   147  
   148  	case *ast.CaseClause:
   149  		return hasBreakList(s.Body, label, implicit)
   150  
   151  	case *ast.SwitchStmt:
   152  		if label != "" && hasBreak(s.Body, label, false) {
   153  			return true
   154  		}
   155  
   156  	case *ast.TypeSwitchStmt:
   157  		if label != "" && hasBreak(s.Body, label, false) {
   158  			return true
   159  		}
   160  
   161  	case *ast.CommClause:
   162  		return hasBreakList(s.Body, label, implicit)
   163  
   164  	case *ast.SelectStmt:
   165  		if label != "" && hasBreak(s.Body, label, false) {
   166  			return true
   167  		}
   168  
   169  	case *ast.ForStmt:
   170  		if label != "" && hasBreak(s.Body, label, false) {
   171  			return true
   172  		}
   173  
   174  	case *ast.RangeStmt:
   175  		if label != "" && hasBreak(s.Body, label, false) {
   176  			return true
   177  		}
   178  	}
   179  
   180  	return false
   181  }
   182  
   183  func hasBreakList(list []ast.Stmt, label string, implicit bool) bool {
   184  	for _, s := range list {
   185  		if hasBreak(s, label, implicit) {
   186  			return true
   187  		}
   188  	}
   189  	return false
   190  }