blob: 8a4210cb390c77ab447bcc67592c05b10b70cf60 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Nikolay Metchev <nikolaymetchev@gmail.com> - [inline] Inline local variable with initializer generates assignment where left-hand side is not a variable - https://bugs.eclipse.org/394721
*******************************************************************************/
package org.eclipse.jdt.internal.corext.dom;
import java.util.Iterator;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.AssertStatement;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.ConditionalExpression;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.InfixExpression.Operator;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.SwitchCase;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.SynchronizedStatement;
import org.eclipse.jdt.core.dom.ThrowStatement;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.WhileStatement;
import org.eclipse.jdt.internal.corext.refactoring.code.OperatorPrecedence;
import org.eclipse.jdt.internal.corext.util.JDTUIHelperClasses;
/**
* Helper class to check if an expression requires parentheses.
*
* @see JDTUIHelperClasses
* @since 3.7
*/
public class NecessaryParenthesesChecker {
/*
* Get the expression wrapped by the parentheses
* i.e. ((((expression)))) -> expression
*/
private static Expression getExpression(ParenthesizedExpression node) {
Expression expression= node.getExpression();
while (expression instanceof ParenthesizedExpression) {
expression= ((ParenthesizedExpression)expression).getExpression();
}
return expression;
}
private static boolean expressionTypeNeedsParentheses(Expression expression) {
int type= expression.getNodeType();
return type == ASTNode.INFIX_EXPRESSION
|| type == ASTNode.CONDITIONAL_EXPRESSION
|| type == ASTNode.PREFIX_EXPRESSION
|| type == ASTNode.POSTFIX_EXPRESSION
|| type == ASTNode.CAST_EXPRESSION
|| type == ASTNode.INSTANCEOF_EXPRESSION
|| type == ASTNode.ARRAY_CREATION
|| type == ASTNode.ASSIGNMENT;
}
private static boolean locationNeedsParentheses(StructuralPropertyDescriptor locationInParent) {
if (locationInParent instanceof ChildListPropertyDescriptor && locationInParent != InfixExpression.EXTENDED_OPERANDS_PROPERTY) {
// e.g. argument lists of MethodInvocation, ClassInstanceCreation, dimensions of ArrayCreation ...
return false;
}
if (locationInParent == VariableDeclarationFragment.INITIALIZER_PROPERTY
|| locationInParent == SingleVariableDeclaration.INITIALIZER_PROPERTY
|| locationInParent == ReturnStatement.EXPRESSION_PROPERTY
|| locationInParent == EnhancedForStatement.EXPRESSION_PROPERTY
|| locationInParent == ForStatement.EXPRESSION_PROPERTY
|| locationInParent == WhileStatement.EXPRESSION_PROPERTY
|| locationInParent == DoStatement.EXPRESSION_PROPERTY
|| locationInParent == AssertStatement.EXPRESSION_PROPERTY
|| locationInParent == AssertStatement.MESSAGE_PROPERTY
|| locationInParent == IfStatement.EXPRESSION_PROPERTY
|| locationInParent == SwitchStatement.EXPRESSION_PROPERTY
|| locationInParent == SwitchCase.EXPRESSION_PROPERTY
|| locationInParent == ArrayAccess.INDEX_PROPERTY
|| locationInParent == ThrowStatement.EXPRESSION_PROPERTY
|| locationInParent == SynchronizedStatement.EXPRESSION_PROPERTY
|| locationInParent == ParenthesizedExpression.EXPRESSION_PROPERTY) {
return false;
}
return true;
}
/*
* Do all operands in expression have same type
*/
private static boolean isAllOperandsHaveSameType(InfixExpression expression, ITypeBinding leftOperandType, ITypeBinding rightOperandType) {
ITypeBinding binding= leftOperandType;
if (binding == null)
return false;
ITypeBinding current= rightOperandType;
if (binding != current)
return false;
for (Iterator<Expression> iterator= expression.extendedOperands().iterator(); iterator.hasNext();) {
Expression operand= iterator.next();
current= operand.resolveTypeBinding();
if (binding != current)
return false;
}
return true;
}
private static boolean isIntegerType(ITypeBinding binding) {
if (binding == null)
return false;
if (!binding.isPrimitive())
return false;
String name= binding.getName();
if (isIntegerNumber(name))
return true;
return false;
}
private static boolean isStringType(ITypeBinding binding) {
if (binding == null)
return false;
return "java.lang.String".equals(binding.getQualifiedName()); //$NON-NLS-1$
}
/*
* Is the given expression associative?
*
* This is true if and only if:<br>
* <code>left operator (right) == (right) operator left == right operator left</code>
*/
private static boolean isAssociative(InfixExpression.Operator operator, ITypeBinding infixExprType, boolean isAllOperandsHaveSameType) {
if (operator == InfixExpression.Operator.PLUS)
return isStringType(infixExprType) || isIntegerType(infixExprType) && isAllOperandsHaveSameType;
if (operator == InfixExpression.Operator.TIMES)
return isIntegerType(infixExprType) && isAllOperandsHaveSameType;
if (operator == InfixExpression.Operator.CONDITIONAL_AND
|| operator == InfixExpression.Operator.CONDITIONAL_OR
|| operator == InfixExpression.Operator.AND
|| operator == InfixExpression.Operator.OR
|| operator == InfixExpression.Operator.XOR)
return true;
return false;
}
private static boolean isIntegerNumber(String name) {
return "int".equals(name) || "long".equals(name) || "byte".equals(name) || "char".equals(name) || "short".equals(name); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
}
private static boolean needsParenthesesInInfixExpression(Expression expression, InfixExpression parentInfix, StructuralPropertyDescriptor locationInParent,
ITypeBinding leftOperandType) {
InfixExpression.Operator parentInfixOperator= parentInfix.getOperator();
ITypeBinding rightOperandType;
ITypeBinding parentInfixExprType;
if (leftOperandType == null) { // parentInfix has bindings
leftOperandType= parentInfix.getLeftOperand().resolveTypeBinding();
rightOperandType= parentInfix.getRightOperand().resolveTypeBinding();
parentInfixExprType= parentInfix.resolveTypeBinding();
} else {
rightOperandType= expression.resolveTypeBinding();
parentInfixExprType= getInfixExpressionType(parentInfixOperator, leftOperandType, rightOperandType);
}
boolean isAllOperandsHaveSameType= isAllOperandsHaveSameType(parentInfix, leftOperandType, rightOperandType);
if (locationInParent == InfixExpression.LEFT_OPERAND_PROPERTY) {
//we have (expr op expr) op expr
//infix expressions are evaluated from left to right -> parentheses not needed
return false;
} else if (isAssociative(parentInfixOperator, parentInfixExprType, isAllOperandsHaveSameType)) {
//we have parent op (expr op expr) and op is associative
//left op (right) == (right) op left == right op left
if (expression instanceof InfixExpression) {
InfixExpression infixExpression= (InfixExpression)expression;
Operator operator= infixExpression.getOperator();
if (isStringType(parentInfixExprType)) {
if (parentInfixOperator == InfixExpression.Operator.PLUS && operator == InfixExpression.Operator.PLUS && isStringType(infixExpression.resolveTypeBinding())) {
// 1 + ("" + 2) == 1 + "" + 2
// 1 + (2 + "") != 1 + 2 + ""
// "" + (2 + "") == "" + 2 + ""
return !isStringType(infixExpression.getLeftOperand().resolveTypeBinding()) && !isStringType(leftOperandType);
}
//"" + (1 + 2), "" + (1 - 2) etc
return true;
}
if (parentInfixOperator != InfixExpression.Operator.TIMES)
return false;
if (operator == InfixExpression.Operator.TIMES)
// x * (y * z) == x * y * z
return false;
if (operator == InfixExpression.Operator.REMAINDER || operator == InfixExpression.Operator.DIVIDE)
// x * (y % z) != x * y % z , x * (y / z) == x * y / z rounding involved
return true;
return false;
}
return false;
} else {
return true;
}
}
/**
* Returns the type of infix expression based on its operands and operator.
*
* @param operator the operator of infix expression
* @param leftOperandType the type of left operand of infix expression
* @param rightOperandType the type of right operand of infix expression
* @return the type of infix expression if the type of both the operands is same or if the type
* of either operand of a + operator is String, <code>null</code> otherwise.
*
* @since 3.9
*/
private static ITypeBinding getInfixExpressionType(InfixExpression.Operator operator, ITypeBinding leftOperandType, ITypeBinding rightOperandType) {
if (leftOperandType == rightOperandType) {
return leftOperandType;
}
if (operator == InfixExpression.Operator.PLUS) {
if (isStringType(leftOperandType)) {
return leftOperandType;
} else if (isStringType(rightOperandType)) {
return rightOperandType;
}
}
// If the left and right operand types are different, we assume that parentheses are needed.
// This is to avoid complications of numeric promotions and for readability of complicated code.
return null;
}
/**
* Can the parentheses be removed from the parenthesized expression ?
*
* <p>
* <b>Note:</b> The parenthesized expression must not be an unparented node.
* </p>
*
* @param expression the parenthesized expression
* @return <code>true</code> if parentheses can be removed, <code>false</code> otherwise.
*/
public static boolean canRemoveParentheses(Expression expression) {
return canRemoveParentheses(expression, expression.getParent(), expression.getLocationInParent());
}
/**
* Can the parentheses be removed from the parenthesized expression when inserted into
* <code>parent</code> at <code>locationInParent</code> ?
*
* <p>
* <b>Note:</b> The parenthesized expression can be an unparented node.
* </p>
*
* @param expression the parenthesized expression
* @param parent the parent node
* @param locationInParent location of expression in the parent
* @return <code>true</code> if parentheses can be removed, <code>false</code> otherwise.
*/
public static boolean canRemoveParentheses(Expression expression, ASTNode parent, StructuralPropertyDescriptor locationInParent) {
if (!(expression instanceof ParenthesizedExpression)) {
return false;
}
return !needsParentheses(getExpression((ParenthesizedExpression)expression), parent, locationInParent);
}
/**
* Does the <code>rightOperand</code> need parentheses when inserted into
* <code>infixExpression</code> ?
*
* <p>
* <b>Note:</b>
* <ul>
* <li>The <code>infixExpression</code> can be a new node (not from a resolved AST) with no
* bindings.</li>
* <li>The <code>infixExpression</code> must not have additional operands.</li>
* <li>The <code>rightOperand</code> node must have bindings.</li>
* </ul>
* </p>
*
* @param rightOperand the right operand in <code>infixExpression</code>
* @param infixExpression the parent infix expression
* @param leftOperandType the type of the left operand in <code>infixExpression</code>
* @return <code>true</code> if <code>rightOperand</code> needs parentheses, <code>false</code>
* otherwise.
*
* @since 3.9
*/
public static boolean needsParenthesesForRightOperand(Expression rightOperand, InfixExpression infixExpression, ITypeBinding leftOperandType) {
return needsParentheses(rightOperand, infixExpression, InfixExpression.RIGHT_OPERAND_PROPERTY, leftOperandType);
}
/**
* Does the <code>expression</code> need parentheses when inserted into <code>parent</code> at
* <code>locationInParent</code> ?
*
* <p>
* <b>Note:</b> The expression can be an unparented node.
* </p>
*
* @param expression the expression
* @param parent the parent node
* @param locationInParent location of expression in the parent
* @return <code>true</code> if <code>expression</code> needs parentheses, <code>false</code>
* otherwise.
*/
public static boolean needsParentheses(Expression expression, ASTNode parent, StructuralPropertyDescriptor locationInParent) {
return needsParentheses(expression, parent, locationInParent, null);
}
/**
* Does the <code>expression</code> need parentheses when inserted into <code>parent</code> at
* <code>locationInParent</code> ?
*
* @param expression the expression
* @param parent the parent node
* @param locationInParent location of expression in the parent
* @param leftOperandType the type of the left operand in <code>parent</code> if
* <code>parent</code> is an infix expression with no bindings and
* <code>expression</code> is the right operand in it, <code>null</code> otherwise
* @return <code>true</code> if <code>expression</code> needs parentheses, <code>false</code>
* otherwise.
*
* @since 3.9
*/
private static boolean needsParentheses(Expression expression, ASTNode parent, StructuralPropertyDescriptor locationInParent, ITypeBinding leftOperandType) {
if (!expressionTypeNeedsParentheses(expression))
return false;
if (!locationNeedsParentheses(locationInParent)) {
return false;
}
if (parent instanceof Expression) {
Expression parentExpression= (Expression)parent;
if (expression instanceof PrefixExpression) { // see bug 405096
return needsParenthesesForPrefixExpression(parentExpression, ((PrefixExpression) expression).getOperator());
}
if (expression instanceof ArrayCreation) { // see bug 394721
return parentExpression instanceof ArrayAccess && ((ArrayCreation) expression).getInitializer() == null;
}
int expressionPrecedence= OperatorPrecedence.getExpressionPrecedence(expression);
int parentPrecedence= OperatorPrecedence.getExpressionPrecedence(parentExpression);
if (expressionPrecedence > parentPrecedence)
//(opEx) opParent and opEx binds more -> parentheses not needed
return false;
if (expressionPrecedence < parentPrecedence)
//(opEx) opParent and opEx binds less -> parentheses needed
return true;
//(opEx) opParent binds equal
if (parentExpression instanceof InfixExpression) {
return needsParenthesesInInfixExpression(expression, (InfixExpression) parentExpression, locationInParent, leftOperandType);
}
if (parentExpression instanceof ConditionalExpression && locationInParent == ConditionalExpression.EXPRESSION_PROPERTY) {
return true;
}
return false;
}
return true;
}
private static boolean needsParenthesesForPrefixExpression(Expression parentExpression, PrefixExpression.Operator expressionOperator) {
if (parentExpression instanceof PrefixExpression) {
PrefixExpression.Operator parentOperator= ((PrefixExpression) parentExpression).getOperator();
if (parentOperator == PrefixExpression.Operator.PLUS &&
(expressionOperator == PrefixExpression.Operator.PLUS || expressionOperator == PrefixExpression.Operator.INCREMENT)) {
return true;
}
if (parentOperator == PrefixExpression.Operator.MINUS &&
(expressionOperator == PrefixExpression.Operator.MINUS || expressionOperator == PrefixExpression.Operator.DECREMENT)) {
return true;
}
} else if (parentExpression instanceof InfixExpression) {
InfixExpression.Operator parentOperator= ((InfixExpression) parentExpression).getOperator();
if (parentOperator == InfixExpression.Operator.PLUS &&
(expressionOperator == PrefixExpression.Operator.PLUS || expressionOperator == PrefixExpression.Operator.INCREMENT)) {
return true;
}
if (parentOperator == InfixExpression.Operator.MINUS &&
(expressionOperator == PrefixExpression.Operator.MINUS || expressionOperator == PrefixExpression.Operator.DECREMENT)) {
return true;
}
}
return false;
}
}