/** 13.12.11 Runtime Semantics: Evaluation */
  @Override
  public Completion visit(SwitchStatement node, StatementVisitor mv) {
    // stack -> switchValue
    ValType switchValueType = expression(node.getExpression(), mv);

    SwitchType type = SwitchType.of(node);
    boolean defaultOrReturn = false;
    if (type == SwitchType.Int) {
      if (!switchValueType.isNumeric() && switchValueType != ValType.Any) {
        defaultOrReturn = true;
      }
    } else if (type == SwitchType.Char) {
      if (switchValueType != ValType.String && switchValueType != ValType.Any) {
        defaultOrReturn = true;
      }
    } else if (type == SwitchType.String) {
      if (switchValueType != ValType.String && switchValueType != ValType.Any) {
        defaultOrReturn = true;
      }
    } else if (type == SwitchType.Generic) {
      mv.toBoxed(switchValueType);
      switchValueType = ValType.Any;
    } else {
      assert type == SwitchType.Default;
      defaultOrReturn = true;
    }

    final boolean defaultClausePresent = hasDefaultClause(node);
    if (defaultOrReturn) {
      // never true -> emit default switch or return
      mv.pop(switchValueType);
      if (defaultClausePresent) {
        type = SwitchType.Default;
      } else {
        return Completion.Normal;
      }
    }

    mv.enterVariableScope();
    Variable<LexicalEnvironment<?>> savedEnv = saveEnvironment(node, mv);

    Variable<?> switchValue = null;
    if (type != SwitchType.Default) {
      switchValue = mv.newVariable("switchValue", switchValueType.toClass());
      mv.store(switchValue);
    }

    BlockScope scope = node.getScope();
    if (scope.isPresent()) {
      newDeclarativeEnvironment(scope, mv);
      codegen.blockInit(node, mv);
      pushLexicalEnvironment(mv);
    }

    Jump lblExit = new Jump();
    BreakLabel lblBreak = new BreakLabel();
    mv.enterScope(node);
    mv.enterBreakable(node, lblBreak);
    Completion result = CaseBlockEvaluation(node, type, lblExit, switchValue, mv);
    mv.exitBreakable(node);
    mv.exitScope();

    if (!defaultClausePresent) {
      mv.mark(lblExit);
    }
    if (scope.isPresent() && !result.isAbrupt()) {
      popLexicalEnvironment(mv);
    }
    if (lblBreak.isTarget()) {
      mv.mark(lblBreak);
      restoreEnvironment(savedEnv, mv);
    }
    mv.exitVariableScope();

    return result.normal(lblBreak.isTarget());
  }