@Override
  public void visitUnaryExpression(GrUnaryExpression expression) {
    final GrExpression operand = expression.getOperand();
    if (operand == null) return;

    if (expression.getOperationTokenType() != mLNOT) {
      operand.accept(this);
      visitCall(expression);
      return;
    }

    ConditionInstruction cond = new ConditionInstruction(expression);
    addNodeAndCheckPending(cond);
    registerCondition(cond);

    operand.accept(this);
    visitCall(expression);

    myConditions.removeFirstOccurrence(cond);

    List<GotoInstruction> negations = collectAndRemoveAllPendingNegations(expression);

    InstructionImpl head = myHead;
    addNodeAndCheckPending(new PositiveGotoInstruction(expression, cond));
    handlePossibleReturn(expression);
    addPendingEdge(expression, myHead);

    if (negations.isEmpty()) {
      myHead = head;
    } else {
      myHead = reduceAllNegationsIntoInstruction(expression, negations);
    }
  }
  @Nullable
  public static PsiType getExpectedClosureReturnType(GrClosableBlock closure) {
    final Set<PsiType> expectedTypes = getDefaultExpectedTypes(closure);

    List<PsiType> expectedReturnTypes = new ArrayList<PsiType>();
    for (PsiType expectedType : expectedTypes) {
      if (!(expectedType instanceof PsiClassType)) return null;

      final PsiClassType.ClassResolveResult resolveResult =
          ((PsiClassType) expectedType).resolveGenerics();
      final PsiClass resolved = resolveResult.getElement();
      if (resolved == null
          || !(GroovyCommonClassNames.GROOVY_LANG_CLOSURE.equals(resolved.getQualifiedName())))
        return null;

      final PsiTypeParameter[] typeParameters = resolved.getTypeParameters();
      if (typeParameters.length != 1) return null;

      final PsiTypeParameter expected = typeParameters[0];
      final PsiType expectedReturnType = resolveResult.getSubstitutor().substitute(expected);
      if (expectedReturnType == PsiType.VOID || expectedReturnType == null) return null;

      expectedReturnTypes.add(expectedReturnType);
    }

    return TypesUtil.getLeastUpperBoundNullable(expectedReturnTypes, closure.getManager());
  }
  public void visitAssertStatement(GrAssertStatement assertStatement) {
    final InstructionImpl assertInstruction = startNode(assertStatement);

    final GrExpression assertion = assertStatement.getAssertion();
    if (assertion != null) {
      assertion.accept(this);

      InstructionImpl positiveHead = myHead;

      List<GotoInstruction> negations = collectAndRemoveAllPendingNegations(assertStatement);
      if (!negations.isEmpty()) {
        interruptFlow();
        reduceAllNegationsIntoInstruction(assertStatement, negations);
      }

      GrExpression errorMessage = assertStatement.getErrorMessage();
      if (errorMessage != null) {
        errorMessage.accept(this);
      }
      addNode(new ThrowingInstruction(assertStatement));

      final PsiType type =
          TypesUtil.createTypeByFQClassName(
              CommonClassNames.JAVA_LANG_ASSERTION_ERROR, assertStatement);
      ExceptionInfo info = findCatch(type);
      if (info != null) {
        info.myThrowers.add(myHead);
      } else {
        addPendingEdge(null, myHead);
      }

      myHead = positiveHead;
    }
    finishNode(assertInstruction);
  }
  private List<GotoInstruction> collectAndRemoveAllPendingNegations(GroovyPsiElement currentScope) {
    List<GotoInstruction> negations = new ArrayList<GotoInstruction>();
    for (Iterator<Pair<InstructionImpl, GroovyPsiElement>> iterator = myPending.iterator();
        iterator.hasNext(); ) {
      Pair<InstructionImpl, GroovyPsiElement> pair = iterator.next();
      InstructionImpl instruction = pair.first;
      GroovyPsiElement scope = pair.second;

      if (!PsiTreeUtil.isAncestor(scope, currentScope, true)
          && instruction instanceof GotoInstruction) {
        negations.add((GotoInstruction) instruction);
        iterator.remove();
      }
    }
    return negations;
  }
 @Nullable
 private InstructionImpl reduceAllNegationsIntoInstruction(
     GroovyPsiElement currentScope, List<? extends GotoInstruction> negations) {
   if (negations.size() > 1) {
     InstructionImpl instruction = addNode(new InstructionImpl(currentScope));
     for (GotoInstruction negation : negations) {
       addEdge(negation, instruction);
     }
     return instruction;
   } else if (negations.size() == 1) {
     GotoInstruction instruction = negations.get(0);
     myHead = instruction;
     return instruction;
   }
   return null;
 }
    @Override
    public void visitSwitchStatement(GrSwitchStatement switchStatement) {
      final GrCaseSection[] sections = switchStatement.getCaseSections();
      List<PsiType> types = new ArrayList<PsiType>(sections.length);
      for (GrCaseSection section : sections) {
        final GrExpression value = section.getCaseLabel().getValue();
        final PsiType type = value != null ? value.getType() : null;
        if (type != null) types.add(type);
      }

      final PsiType upperBoundNullable =
          TypesUtil.getLeastUpperBoundNullable(types, switchStatement.getManager());
      if (upperBoundNullable == null) return;

      myResult = new TypeConstraint[] {SubtypeConstraint.create(upperBoundNullable)};
    }
    private void addConstraintsFromMap(
        List<TypeConstraint> constraints, Map<GrExpression, Pair<PsiParameter, PsiType>> map) {
      if (map == null) return;

      final Pair<PsiParameter, PsiType> pair = map.get(myExpression);
      if (pair == null) return;

      final PsiType type = pair.second;
      if (type == null) return;

      constraints.add(SubtypeConstraint.create(type));

      if (type instanceof PsiArrayType && pair.first.isVarArgs()) {
        constraints.add(SubtypeConstraint.create(((PsiArrayType) type).getComponentType()));
      }
    }
 private <T extends InstructionImpl> T addNode(T instruction) {
   instruction.setNumber(myInstructionNumber++);
   myInstructions.add(instruction);
   if (myHead != null) {
     addEdge(myHead, instruction);
   }
   myHead = instruction;
   return instruction;
 }
 public void visitArgumentList(GrArgumentList list) {
   List<TypeConstraint> constraints = new ArrayList<TypeConstraint>();
   for (GroovyResolveResult variant : ResolveUtil.getCallVariants(list)) {
     final Map<GrExpression, Pair<PsiParameter, PsiType>> map =
         GrClosureSignatureUtil.mapArgumentsToParameters(
             variant,
             list,
             true,
             true,
             list.getNamedArguments(),
             list.getExpressionArguments(),
             GrClosableBlock.EMPTY_ARRAY);
     addConstraintsFromMap(constraints, map);
   }
   if (!constraints.isEmpty()) {
     myResult = constraints.toArray(new TypeConstraint[constraints.size()]);
   }
 }
 @Override
 public void visitListOrMap(GrListOrMap listOrMap) {
   if (listOrMap.isMap()) return;
   final TypeConstraint[] constraints = calculateTypeConstraints(listOrMap);
   List<PsiType> result = new ArrayList<PsiType>(constraints.length);
   for (TypeConstraint constraint : constraints) {
     if (constraint instanceof SubtypeConstraint) {
       final PsiType type = constraint.getType();
       final PsiType iterable =
           com.intellij.psi.util.PsiUtil.extractIterableTypeParameter(type, true);
       if (iterable != null) {
         result.add(iterable);
       }
     }
   }
   if (result.size() == 0) {
     myResult = TypeConstraint.EMPTY_ARRAY;
   } else {
     myResult = new TypeConstraint[result.size()];
     for (int i = 0; i < result.size(); i++) {
       final PsiType type = result.get(i);
       if (type != null) {
         myResult[i] = SubtypeConstraint.create(type);
       }
     }
   }
 }
    public void visitMethodCallExpression(GrMethodCallExpression methodCall) {
      final GrExpression invokedExpression = methodCall.getInvokedExpression();
      if (myExpression.equals(invokedExpression)) {
        myResult =
            new TypeConstraint[] {
              SubtypeConstraint.create(GroovyCommonClassNames.GROOVY_LANG_CLOSURE, methodCall)
            };
        return;
      }

      final GrClosableBlock[] closureArgs = methodCall.getClosureArguments();
      //noinspection SuspiciousMethodCalls
      final int closureIndex = Arrays.asList(closureArgs).indexOf(myExpression);
      if (closureIndex >= 0) {
        List<TypeConstraint> constraints = new ArrayList<TypeConstraint>();
        for (GroovyResolveResult variant : ResolveUtil.getCallVariants(myExpression)) {
          final GrArgumentList argumentList = methodCall.getArgumentList();
          final GrNamedArgument[] namedArgs =
              argumentList == null ? GrNamedArgument.EMPTY_ARRAY : argumentList.getNamedArguments();
          final GrExpression[] expressionArgs =
              argumentList == null
                  ? GrExpression.EMPTY_ARRAY
                  : argumentList.getExpressionArguments();
          try {
            final Map<GrExpression, Pair<PsiParameter, PsiType>> map =
                GrClosureSignatureUtil.mapArgumentsToParameters(
                    variant, methodCall, true, true, namedArgs, expressionArgs, closureArgs);
            addConstraintsFromMap(constraints, map);
          } catch (RuntimeException e) {
            LOG.error(
                "call: " + methodCall.getText() + "\nsymbol: " + variant.getElement().getText(), e);
          }
        }
        if (!constraints.isEmpty()) {
          myResult = constraints.toArray(new TypeConstraint[constraints.size()]);
        }
      }
    }
  public Instruction[] buildControlFlow(GroovyPsiElement scope) {
    myInstructions = new ArrayList<InstructionImpl>();
    myProcessingStack = new ArrayDeque<InstructionImpl>();
    myCaughtExceptionInfos = new ArrayDeque<ExceptionInfo>();
    myConditions = new ArrayDeque<ConditionInstruction>();

    myFinallyCount = 0;
    myPending = new ArrayList<Pair<InstructionImpl, GroovyPsiElement>>();
    myInstructionNumber = 0;

    myScope = scope;

    startNode(null);
    if (scope instanceof GrClosableBlock) {
      buildFlowForClosure((GrClosableBlock) scope);
    } else {
      scope.accept(this);
    }

    final InstructionImpl end = startNode(null);
    checkPending(end); // collect return edges

    return assertValidPsi(myInstructions.toArray(new Instruction[myInstructions.size()]));
  }
  @Override
  public void visitBinaryExpression(GrBinaryExpression expression) {
    final GrExpression left = expression.getLeftOperand();
    final GrExpression right = expression.getRightOperand();
    final IElementType opType = expression.getOperationTokenType();

    if (ControlFlowBuilderUtil.isInstanceOfBinary(expression)) {
      expression.getLeftOperand().accept(this);
      processInstanceOf(expression);
      return;
    }

    if (opType != mLOR && opType != mLAND && opType != kIN) {
      left.accept(this);
      if (right != null) {
        right.accept(this);
      }
      visitCall(expression);
      return;
    }

    ConditionInstruction condition = new ConditionInstruction(expression);
    addNodeAndCheckPending(condition);
    registerCondition(condition);

    left.accept(this);

    if (right == null) return;

    final List<GotoInstruction> negations = collectAndRemoveAllPendingNegations(expression);

    visitCall(expression);

    if (opType == mLAND) {
      InstructionImpl head = myHead;
      if (negations.isEmpty()) {
        addNode(new NegatingGotoInstruction(expression, condition));
        handlePossibleReturn(expression);
        addPendingEdge(expression, myHead);
      } else {
        for (GotoInstruction negation : negations) {
          myHead = negation;
          handlePossibleReturn(expression);
          addPendingEdge(expression, myHead);
        }
      }
      myHead = head;
    } else /*if (opType == mLOR)*/ {
      final InstructionImpl instruction =
          addNodeAndCheckPending(
              new InstructionImpl(expression)); // collect all pending edges from left argument
      handlePossibleReturn(expression);
      addPendingEdge(expression, myHead);
      myHead = instruction;

      InstructionImpl head = reduceAllNegationsIntoInstruction(expression, negations);
      if (head != null) myHead = head;
      // addNode(new NegatingGotoInstruction(expression, myInstructionNumber++, condition));
    }
    myConditions.removeFirstOccurrence(condition);

    right.accept(this);
  }