Repository: vyperlang/vyper

Issue:
`continue` outside of loop context results in compiler panic
### Version Information

* vyper Version (output of `vyper --version`): 0.2.16
* OS: Arch Linux x86_64
* Python Version (output of `python --version`): Python 3.10.4

### What's your issue about?
Unhandled internal compiler error

Please include information like:

* full output of the error you received
> vyper.exceptions.CompilerPanic: Invalid break
> This is an unhandled internal compiler error. Please create an issue on Github to notify the developers.
> https://github.com/vyperlang/vyper/issues/new?template=bug.md

* what command you ran
 vyper interface.vy > bin/interface.bin

* the code that caused the failure (see [this link](https://help.github.com/articles/basic-writing-and-formatting-syntax/) for help with formatting code)
```vyper
# @version 0.2.16
# @author maka
# @title interFaceStation
# @notice fun with interfaces

interface IERC20:
  def totalSupply() -> uint256: view
  def balanceOf(account: address) -> uint256: view
  def transfer(recipient: address, amount: uint256) -> bool: nonpayable
  def allowance(owner: address, spender: address) -> uint256: view
  def approve(spender: address, amount: uint256) -> bool: nonpayable
  def transferFrom(sender: address, recipient: address, amount: uint256) -> bool: nonpayable


event Withdraw:
  token: address
  amount: uint256

event Exchange:
  tokenIn: address
  tokenOut: address
  skimmed: bool


admin: address
weth: public(address)
router: public(address)



@external
def __init__(_weth: address, _router: address):
  self.admin = msg.sender 
  self.weth = _weth
  self.router = _router


# @notice you can deposit eth via direct transfer
#  fallback will be called, and any eth will be auto wrapped ready to swap 
@external
@payable
def __default__():
  raw_call(
    self.weth,
    method_id('deposit()'),
    value = msg.value,
    max_outsize=0,
    is_delegate_call=False
  )


# just playing around
@view
@external
def getSupply(_token: address) -> uint256:
  return IERC20(_token).totalSupply()

@view
@external
def getBalance(_token: address, _account: address) -> uint256:
  return IERC20(_token).balanceOf(_account)


# @notice can only be called by a function that does do a check on caller
@internal
def transfer(_token: address, _recipient: address, _amount: uint256) -> bool:
  return IERC20(_token).transfer(_recipient, _amount)

@external
def withdrawAll(_token: address):
  assert msg.sender == self.admin
  amount: uint256 = IERC20(_token).balanceOf(self)
  self.transfer(_token, self.admin, amount)
  log Withdraw(_token, amount)

# @params
# tokenIn: address of token to swap from
# tokenOut: address of token to swap to
# amountIn: exact amount of input token to be swapped
# amountOutMin: minimum amount of output token willing to receive
# skim: bento style from wallet toggle, false if from contracts balance
#
@external
def swap(_tokenIn: address, _tokenOut: address, _amountIn: uint256, _amountOutMin: uint256, 
  _skim: bool)-> Bytes[128]:
  assert msg.sender == self.admin

  if _skim == True:
    assert IERC20(_tokenIn).balanceOf(msg.sender) >= _amountIn
    IERC20(_tokenIn).transferFrom(msg.sender, self, _amountIn)
  elif _skim == False:
    continue

  approved: bool = IERC20(_tokenIn).approve(self.router, _amountIn)
  assert approved == True

  # @notice we raw_call the routers swap function as it needs a dynamic array of addresses
  #  the value passed will be the number of args (including offset) multiplied by 32
  # @dev pass offset in place of dynamic array, then at offset pass length of the array
  #  arguments of the array will be the final bytes
  response: Bytes[128] = raw_call(
    self.router,
    concat(
      method_id("swapExactTokensForTokens(uint256,uint256,address[],address,uint256)"),
      convert(_amountIn, bytes32),       # exact amount to swap
      convert(_amountOutMin, bytes32),   # minimum amount to receieve (max slippage)
      convert(160, bytes32),             # offset for path is number of arguments*bytes32
      convert(self, bytes32),            # destination for tokens
      convert(block.timestamp, bytes32), # deadline
      convert(2, bytes32),               # number of args in the path
      convert(_tokenIn, bytes32),        # args in the path...
      convert(_tokenOut, bytes32)        # ...
    ),
    is_delegate_call=False,
    max_outsize=128
  )
  log Exchange(_tokenIn, _tokenOut, _skim)
  return response


@external
def sweep():
  assert msg.sender == self.admin
  selfdestruct(self.admin)


# 1 love
```
* please try running your example with the --debug flag turned on
``` Traceback (most recent call last):
  File "/home/c/.local/bin/vyper", line 8, in <module>
    sys.exit(_parse_cli_args())
  File "/home/c/.local/lib/python3.10/site-packages/vyper/cli/vyper_compile.py", line 55, in _parse_cli_args
    return _parse_args(sys.argv[1:])
  File "/home/c/.local/lib/python3.10/site-packages/vyper/cli/vyper_compile.py", line 129, in _parse_args
    compiled = compile_files(
  File "/home/c/.local/lib/python3.10/site-packages/vyper/cli/vyper_compile.py", line 242, in compile_files
    compiler_data = vyper.compile_codes(
  File "/home/c/.local/lib/python3.10/site-packages/vyper/evm/opcodes.py", line 224, in _wrapper
    return fn(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/compiler/__init__.py", line 110, in compile_codes
    exc_handler(contract_name, exc)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/cli/vyper_compile.py", line 166, in exc_handler
    raise exception
  File "/home/c/.local/lib/python3.10/site-packages/vyper/compiler/__init__.py", line 107, in compile_codes
    out[contract_name][output_format] = OUTPUT_FORMATS[output_format](compiler_data)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/compiler/output.py", line 178, in build_bytecode_output
    return f"0x{compiler_data.bytecode.hex()}"
  File "/home/c/.local/lib/python3.10/site-packages/vyper/compiler/phases.py", line 146, in bytecode
    self._bytecode = generate_bytecode(self.assembly)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/compiler/phases.py", line 134, in assembly
    self._assembly = generate_assembly(self.lll_nodes, self.use_ovm)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/compiler/phases.py", line 277, in generate_assembly
    assembly = compile_lll.compile_to_assembly(lll_nodes)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 111, in compile_to_assembly
    res = _compile_to_assembly(code)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 307, in _compile_to_assembly
    o.extend(_compile_to_assembly(arg, withargs, existing_labels, break_dest, height))
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 134, in _compile_to_assembly
    o.extend(_compile_to_assembly(c, withargs, existing_labels, break_dest, height + i))
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 286, in _compile_to_assembly
    lll = _compile_to_assembly(code.args[0], {}, existing_labels, None, 0)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 307, in _compile_to_assembly
    o.extend(_compile_to_assembly(arg, withargs, existing_labels, break_dest, height))
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 268, in _compile_to_assembly
    _compile_to_assembly(code.args[2], withargs, existing_labels, break_dest, height + 1,)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 307, in _compile_to_assembly
    o.extend(_compile_to_assembly(arg, withargs, existing_labels, break_dest, height))
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 187, in _compile_to_assembly
    o.extend(_compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height))
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 307, in _compile_to_assembly
    o.extend(_compile_to_assembly(arg, withargs, existing_labels, break_dest, height))
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 199, in _compile_to_assembly
    o.extend(_compile_to_assembly(code.args[2], withargs, existing_labels, break_dest, height))
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 307, in _compile_to_assembly
    o.extend(_compile_to_assembly(arg, withargs, existing_labels, break_dest, height))
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 187, in _compile_to_assembly
    o.extend(_compile_to_assembly(code.args[1], withargs, existing_labels, break_dest, height))
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 307, in _compile_to_assembly
    o.extend(_compile_to_assembly(arg, withargs, existing_labels, break_dest, height))
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 99, in apply_line_no_wrapper
    ret = func(*args, **kwargs)
  File "/home/c/.local/lib/python3.10/site-packages/vyper/lll/compile_lll.py", line 246, in _compile_to_assembly
    raise CompilerPanic("Invalid break")
```
### How can it be fixed?
Remove the 'continue' in the elif, or entire elif. Not needed



Discussion:
minimal repro:
```python
@external
def foo():
    if True:
        continue
```

this should throw a typechecker error instead of waiting all the way into assembly generation.

Resolving patch:
diff --git a/vyper/semantics/validation/local.py b/vyper/semantics/validation/local.py
--- a/vyper/semantics/validation/local.py
+++ b/vyper/semantics/validation/local.py
@@ -156,7 +156,6 @@ class FunctionNodeVisitor(VyperNodeVisitorBase):
     ignored_types = (
         vy_ast.Break,
         vy_ast.Constant,
-        vy_ast.Continue,
         vy_ast.Pass,
     )
     scope_name = "function"
@@ -269,6 +268,11 @@ def visit_Assert(self, node):
             raise InvalidType("Assertion test value must be a boolean", node.test)
         self.expr_visitor.visit(node.test)
 
+    def visit_Continue(self, node):
+        for_node = node.get_ancestor(vy_ast.For)
+        if for_node is None:
+            raise StructureException("`continue` must be enclosed in a `for` loop", node)
+
     def visit_Return(self, node):
         values = node.value
         if values is None:


Test patch:
diff --git a/tests/parser/features/iteration/test_continue.py b/tests/parser/features/iteration/test_continue.py
--- a/tests/parser/features/iteration/test_continue.py
+++ b/tests/parser/features/iteration/test_continue.py
@@ -1,3 +1,6 @@
+import pytest
+
+
 def test_continue1(get_contract_with_gas_estimation):
     code = """
 @external
@@ -53,3 +56,32 @@ def foo() -> int128:
 """
     c = get_contract_with_gas_estimation(code)
     assert c.foo() == 3
+
+
+fail_list = [
+    """
+@external
+def foo():
+    a: uint256 = 3
+    continue
+    """,
+    """
+@external
+def foo():
+    if True:
+        continue
+    """,
+    """
+@external
+def foo():
+    for i in [1, 2, 3]:
+        b: uint256 = i
+    if True:
+        continue
+    """,
+]
+
+
+@pytest.mark.parametrize("bad_code", fail_list)
+def test_block_fail(assert_compile_failed, get_contract_with_gas_estimation, bad_code):
+    assert_compile_failed(lambda: get_contract_with_gas_estimation(bad_code))