def needParentheses()

in scala/scala-impl/src/org/jetbrains/plugins/scala/lang/psi/ScalaPsiUtil.scala [898:1047]


  def needParentheses(from: ScExpression, expr: ScExpression): Boolean = {
    def infixInInfixParentheses(parent: ScInfixExpr, child: ScInfixExpr): Boolean = {

      import org.jetbrains.plugins.scala.lang.parser.util.ParserUtils._

      if (parent.left == from) {
        val lid = parent.operation.getText
        val rid = child.operation.getText
        if (priority(lid) < priority(rid)) true
        else if (priority(rid) < priority(lid)) false
        else if (operatorAssociativity(lid) != operatorAssociativity(rid)) true
        else if (operatorAssociativity(lid) == Associativity.Right) true
        else false
      }
      else {
        val lid = child.operation.getText
        val rid = parent.operation.getText
        if (priority(lid) < priority(rid)) false
        else if (priority(rid) < priority(lid)) true
        else if (operatorAssociativity(lid) != operatorAssociativity(rid)) true
        else if (operatorAssociativity(lid) == Associativity.Right) false
        else true
      }
    }

    def tupleInInfixNeedParentheses(parent: ScInfixExpr, from: ScExpression): Boolean = {
      if (from.getParent != parent) throw new IllegalArgumentException
      if (from == parent.left) false
      else {
        parent.operation.bind() match {
          case Some(resolveResult) =>
            val fromLength         = from.getTextLength
            val startInParent      = from.getStartOffsetInParent
            val endInParent        = startInParent + fromLength
            val parentText         = parent.getText

            val modifiedParentText =
              parentText.substring(0, startInParent) +
                from.getText.substring(1, fromLength - 1) +
                parentText.substring(endInParent)

            val modifiedParent     = createExpressionWithContextFromText(modifiedParentText, parent.getContext)
            modifiedParent match {
              case ScInfixExpr(_, newOper, _: ScTuple) =>
                newOper.bind() match {
                  case Some(newResolveResult) =>
                    newResolveResult.getElement == resolveResult.getElement && newResolveResult.tuplingUsed
                  case _ => true
                }
              case _ => true
            }
          case _ => false
        }
      }
    }

    def parsedDifferently(from: ScExpression, expr: ScExpression): Boolean = {
      if (newLinesEnabled(from)) {
        expr.getParent match {
          case ScParenthesisedExpr(_) =>
            val text = expr.getText
            val dummyFile = createScalaFileFromText(text, expr)(expr.getManager)
            dummyFile.firstChild match {
              case Some(newExpr: ScExpression) => !newExpr.textMatches(text)
              case _ => true
            }
          case _ => false
        }
      } else false
    }

    /**
     * This method determines whether we're dealing with a case like this:
     * {{{
     * if (false)
     *   (if (false) println(1) else if (false) println(2))
     * else println(3)
     * }}}
     *
     * More specifically, we are interested in testing whether the parentheses around
     * `if (false) println(1) else if (false) println(2)` are necessary.
     *
     * There are 2 conditions that determine whether removal of these parentheses leads
     * to changed semantics:
     *
     * The first and more obvious condition, is that the outer if-expression must have
     * an else-expression. If it does not, one can safely remove the parentheses.
     *
     * The second condition is less obvious, but becomes clear after considering that
     * the parenthesized expression is potentially a chain of if-expressions, linked
     * together via the else-construct.
     * To figure out whether the total inner if-expression needs its parentheses, we must
     * recurse the chain until its end and check that the final if-expression has an
     * else-expression.
     * If it does, we can remove the parentheses without changing semantics. If it doesn't,
     * and the outer if-expression indeed has an else-expression, removing the parentheses
     * would associate that else-expression with the formerly parenthesized expression.
     */
    def innerIfNeedsParentheses(outerIf: ScIf, innerIf: ScIf): Boolean = {

      @tailrec
      def ifChainHasTerminatingElse(ifExpr: ScIf): Boolean = ifExpr.elseExpression match {
          case Some(nestedIfExpr: ScIf) => ifChainHasTerminatingElse(nestedIfExpr)
          case Some(_) => true
          case _ => false
        }

      outerIf.elseExpression.nonEmpty && !ifChainHasTerminatingElse(innerIf)
    }

    if (parsedDifferently(from, expr)) true
    else {
      val parent = from.getParent
      (parent, expr) match {
        //order of these case clauses is important!
        case (outerIf: ScIf, innerIf: ScIf) if innerIfNeedsParentheses(outerIf, innerIf) => true
        case (_: ScGuard, _: ScMatch) => true
        case _ if !parent.is[ScExpression] => false
        case _ if expr.textMatches("_") => false
        case (_: ScTuple | _: ScNamedTuple | _: ScBlock | _: ScXmlExpr, _) => false
        case (infix: ScInfixExpr, call: ScMethodCall) if infix.left == from && call.args.isColonArgs => true
        case (infix: ScInfixExpr, nested @ ScParenthesisedExpr(_: ScTuple)) => tupleInInfixNeedParentheses(infix, nested)
        case (_: ScSugarCallExpr |
              _: ScReferenceExpression |
              _: ScTypedExpression, elem: PsiElement)
          if from.isInstanceOf[ScParenthesizedElement] && ScUnderScoreSectionUtil.isUnderscoreFunction(elem) => true
        case (_, _: ScReferenceExpression | _: ScMethodCall |
                 _: ScGenericCall | _: ScLiteral | _: ScTuple | _: ScNamedTuple |
                 _: ScXmlExpr | _: ScParenthesisedExpr | _: ScUnitExpr |
                 _: ScThisReference | _: ScSuperReference) => false
        case (_: ScMethodCall | _: ScUnderscoreSection, _) => true
        case (_, _: ScBlock) => false
        case (_: ScGenericCall, _) => true
        case (_: ScReferenceExpression, _: ScNewTemplateDefinition) =>
          val lastChar: Char = expr.getText.last
          lastChar != ')' && lastChar != '}' && lastChar != ']'
        case (_: ScReferenceExpression, _) => true
        case (_, _: ScNewTemplateDefinition |
                 _: ScUnderscoreSection) => false
        case (_: ScPrefixExpr, _) => true
        case (_, _: ScPrefixExpr) => false
        case (par: ScInfixExpr, child: ScInfixExpr) => infixInInfixParentheses(par, child)
        case (_, _: ScInfixExpr) => false
        case (_: ScPostfixExpr | _: ScInfixExpr, _) => true
        case (_, _: ScPostfixExpr) => false
        case (_: ScTypedExpression | _: ScMatch, _) => true
        case _ => false
      }
    }
  }