Copyright (c) 2011, 2019 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation Nikolay Metchev - [inline] Inline local variable with initializer generates assignment where left-hand side is not a variable - https://bugs.eclipse.org/394721
/******************************************************************************* * Copyright (c) 2011, 2019 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * 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.core.manipulation.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;
Helper class to check if an expression requires parentheses.
See Also:
  • JDTUIHelperClasses
Since:3.7
/** * 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 == SwitchCase.EXPRESSIONS2_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.
Params:
  • operator – the operator of infix expression
  • leftOperandType – the type of left operand of infix expression
  • rightOperandType – the type of right operand of infix expression
Returns: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, null otherwise.
Since:3.9
/** * 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 ?

Note: The parenthesized expression must not be an unparented node.

Params:
  • expression – the parenthesized expression
Returns:true if parentheses can be removed, false otherwise.
/** * 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 parent at locationInParent ?

Note: The parenthesized expression can be an unparented node.

Params:
  • expression – the parenthesized expression
  • parent – the parent node
  • locationInParent – location of expression in the parent
Returns:true if parentheses can be removed, false otherwise.
/** * 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 rightOperand need parentheses when inserted into infixExpression ?

Note:

  • The infixExpression can be a new node (not from a resolved AST) with no bindings.
  • The infixExpression must not have additional operands.
  • The rightOperand node must have bindings.

Params:
  • rightOperand – the right operand in infixExpression
  • infixExpression – the parent infix expression
  • leftOperandType – the type of the left operand in infixExpression
Returns:true if rightOperand needs parentheses, false otherwise.
Since:3.9
/** * 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 expression need parentheses when inserted into parent at locationInParent ?

Note: The expression can be an unparented node.

Params:
  • expression – the expression
  • parent – the parent node
  • locationInParent – location of expression in the parent
Returns:true if expression needs parentheses, false otherwise.
/** * 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 expression need parentheses when inserted into parent at locationInParent ?
Params:
  • expression – the expression
  • parent – the parent node
  • locationInParent – location of expression in the parent
  • leftOperandType – the type of the left operand in parent if parent is an infix expression with no bindings and expression is the right operand in it, null otherwise
Returns:true if expression needs parentheses, false otherwise.
Since:3.9
/** * 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; } }