Repository: python/mypy

Issue:
crash when type checking a `break` outside loop
On the program

``` py
break
```

mypy crashes with the error

```
Traceback (most recent call last):
  File "/Users/rbarton/mypy/scripts/mypy", line 6, in <module>
    main(__file__)
[...]
  File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/mypy/checker.py", line 2028, in visit_break_stmt
    self.binder.allow_jump(self.binder.loop_frames[-1] - 1)
IndexError: list index out of range

*** INTERNAL ERROR ***

break.py:1: error: Internal error -- please report a bug at https://github.com/python/mypy/issues

NOTE: you can use "mypy --pdb ..." to drop into the debugger when this happens.
```

Semantic analysis actually identifies that there is a `break` outside a loop and "fails", but this failure isn't fatal and mypy goes on to type check the module and crashes before printing the error. Not sure what the cleanest fix is.



Discussion:
Can't we just make that a fatal error?

Making it a fatal error sounds reasonable.


Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -1651,11 +1651,11 @@ def visit_for_stmt(self, s: ForStmt) -> None:
 
     def visit_break_stmt(self, s: BreakStmt) -> None:
         if self.loop_depth == 0:
-            self.fail("'break' outside loop", s, True)
+            self.fail("'break' outside loop", s, True, blocker=True)
 
     def visit_continue_stmt(self, s: ContinueStmt) -> None:
         if self.loop_depth == 0:
-            self.fail("'continue' outside loop", s, True)
+            self.fail("'continue' outside loop", s, True, blocker=True)
 
     def visit_if_stmt(self, s: IfStmt) -> None:
         infer_reachability_of_if_statement(s, pyversion=self.pyversion)
@@ -1783,7 +1783,7 @@ def visit_star_expr(self, expr: StarExpr) -> None:
 
     def visit_yield_from_expr(self, e: YieldFromExpr) -> None:
         if not self.is_func_scope():  # not sure
-            self.fail("'yield from' outside function", e)
+            self.fail("'yield from' outside function", e, True, blocker=True)
         else:
             self.function_stack[-1].is_generator = True
         if e.expr:
@@ -2036,7 +2036,7 @@ def visit__promote_expr(self, expr: PromoteExpr) -> None:
 
     def visit_yield_expr(self, expr: YieldExpr) -> None:
         if not self.is_func_scope():
-            self.fail("'yield' outside function", expr)
+            self.fail("'yield' outside function", expr, True, blocker=True)
         else:
             self.function_stack[-1].is_generator = True
         if expr.expr:
@@ -2250,13 +2250,14 @@ def name_not_defined(self, name: str, ctx: Context) -> None:
     def name_already_defined(self, name: str, ctx: Context) -> None:
         self.fail("Name '{}' already defined".format(name), ctx)
 
-    def fail(self, msg: str, ctx: Context, serious: bool = False) -> None:
+    def fail(self, msg: str, ctx: Context, serious: bool = False, *,
+             blocker: bool = False) -> None:
         if (not serious and
                 not self.check_untyped_defs and
                 self.function_stack and
                 self.function_stack[-1].is_dynamic()):
             return
-        self.errors.report(ctx.get_line(), msg)
+        self.errors.report(ctx.get_line(), msg, blocker=blocker)
 
     def note(self, msg: str, ctx: Context) -> None:
         if (not self.check_untyped_defs and


Test patch:
diff --git a/test-data/unit/check-semanal-error.test b/test-data/unit/check-semanal-error.test
--- a/test-data/unit/check-semanal-error.test
+++ b/test-data/unit/check-semanal-error.test
@@ -66,3 +66,16 @@ T = TypeVar('T')
 class C:  # Forgot to add type params here
     def __init__(self, t: T) -> None: pass
 c = C(t=3)  # type: C[int]  # E: "C" expects no type arguments, but 1 given
+
+[case testBreakOutsideLoop]
+break # E: 'break' outside loop
+
+[case testContinueOutsideLoop]
+continue # E: 'continue' outside loop
+
+[case testYieldOutsideFunction]
+yield # E: 'yield' outside function
+
+[case testYieldFromOutsideFunction]
+x = 1
+yield from x # E: 'yield from' outside function