blob: 150590936b1e465efb04f81dffd4147e0964c9d0 [file] [log] [blame]
/*
* Copyright 2003-2014 Dave Griffith, Bas Leijdekkers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.siyeh.ig.psiutils;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Set;
public class ExpectedTypeUtils {
private ExpectedTypeUtils() {}
@Nullable
public static PsiType findExpectedType(@NotNull PsiExpression expression, boolean calculateTypeForComplexReferences) {
return findExpectedType(expression, calculateTypeForComplexReferences, false);
}
public static PsiType findExpectedType(PsiExpression expression, boolean calculateTypeForComplexReferences, boolean reportCasts) {
PsiElement context = expression.getParent();
PsiExpression wrappedExpression = expression;
while (context instanceof PsiParenthesizedExpression) {
wrappedExpression = (PsiExpression)context;
context = context.getParent();
}
if (context == null) {
return null;
}
final ExpectedTypeVisitor visitor = new ExpectedTypeVisitor(wrappedExpression, calculateTypeForComplexReferences, reportCasts);
context.accept(visitor);
return visitor.getExpectedType();
}
private static class ExpectedTypeVisitor extends JavaElementVisitor {
/**
* @noinspection StaticCollection
*/
private static final Set<IElementType> arithmeticOps = new HashSet<IElementType>(5);
private static final Set<IElementType> booleanOps = new HashSet<IElementType>(5);
private static final Set<IElementType> shiftOps = new HashSet<IElementType>(3);
private static final Set<IElementType> operatorAssignmentOps = new HashSet<IElementType>(11);
static {
arithmeticOps.add(JavaTokenType.PLUS);
arithmeticOps.add(JavaTokenType.MINUS);
arithmeticOps.add(JavaTokenType.ASTERISK);
arithmeticOps.add(JavaTokenType.DIV);
arithmeticOps.add(JavaTokenType.PERC);
booleanOps.add(JavaTokenType.ANDAND);
booleanOps.add(JavaTokenType.AND);
booleanOps.add(JavaTokenType.XOR);
booleanOps.add(JavaTokenType.OROR);
booleanOps.add(JavaTokenType.OR);
shiftOps.add(JavaTokenType.LTLT);
shiftOps.add(JavaTokenType.GTGT);
shiftOps.add(JavaTokenType.GTGTGT);
operatorAssignmentOps.add(JavaTokenType.PLUSEQ);
operatorAssignmentOps.add(JavaTokenType.MINUSEQ);
operatorAssignmentOps.add(JavaTokenType.ASTERISKEQ);
operatorAssignmentOps.add(JavaTokenType.DIVEQ);
operatorAssignmentOps.add(JavaTokenType.ANDEQ);
operatorAssignmentOps.add(JavaTokenType.OREQ);
operatorAssignmentOps.add(JavaTokenType.XOREQ);
operatorAssignmentOps.add(JavaTokenType.PERCEQ);
operatorAssignmentOps.add(JavaTokenType.LTLTEQ);
operatorAssignmentOps.add(JavaTokenType.GTGTEQ);
operatorAssignmentOps.add(JavaTokenType.GTGTGTEQ);
}
@NotNull private final PsiExpression wrappedExpression;
private final boolean calculateTypeForComplexReferences;
private final boolean reportCasts;
private PsiType expectedType = null;
ExpectedTypeVisitor(@NotNull PsiExpression wrappedExpression, boolean calculateTypeForComplexReferences, boolean reportCasts) {
this.wrappedExpression = wrappedExpression;
this.calculateTypeForComplexReferences = calculateTypeForComplexReferences;
this.reportCasts = reportCasts;
}
public PsiType getExpectedType() {
return expectedType;
}
@Override
public void visitField(@NotNull PsiField field) {
final PsiExpression initializer = field.getInitializer();
if (wrappedExpression.equals(initializer)) {
expectedType = field.getType();
}
}
@Override
public void visitVariable(@NotNull PsiVariable variable) {
expectedType = variable.getType();
}
@Override
public void visitAssertStatement(PsiAssertStatement statement) {
final PsiExpression condition = statement.getAssertCondition();
if (wrappedExpression == condition) {
expectedType = PsiType.BOOLEAN;
}
else {
expectedType = TypeUtils.getStringType(statement);
}
}
@Override
public void visitArrayInitializerExpression(PsiArrayInitializerExpression initializer) {
final PsiType type = initializer.getType();
if (!(type instanceof PsiArrayType)) {
expectedType = null;
return;
}
final PsiArrayType arrayType = (PsiArrayType)type;
expectedType = arrayType.getComponentType();
}
@Override
public void visitArrayAccessExpression(PsiArrayAccessExpression accessExpression) {
final PsiExpression indexExpression = accessExpression.getIndexExpression();
if (wrappedExpression.equals(indexExpression)) {
expectedType = PsiType.INT;
}
}
@Override
public void visitPolyadicExpression(@NotNull PsiPolyadicExpression polyadicExpression) {
final PsiExpression[] operands = polyadicExpression.getOperands();
if (operands.length < 2) {
expectedType = null;
return;
}
for (PsiExpression operand : operands) {
if (operand == null || operand.getType() == null) {
expectedType = null;
return;
}
}
final IElementType tokenType = polyadicExpression.getOperationTokenType();
final PsiType type = polyadicExpression.getType();
final PsiType wrappedExpressionType = wrappedExpression.getType();
if (TypeUtils.isJavaLangString(type) || isArithmeticOperation(tokenType) || isBooleanOperation(tokenType)) {
expectedType = type;
}
else if (isShiftOperation(tokenType)) {
expectedType = unaryNumericPromotion(wrappedExpressionType);
}
else if (ComparisonUtils.isEqualityComparison(polyadicExpression)) {
// JLS 15.21.1 Numerical Equality Operators == and !=
if (TypeConversionUtil.isPrimitiveAndNotNull(wrappedExpressionType)) {
expectedType = wrappedExpressionType;
}
else if (operands[0] == wrappedExpression) {
if (TypeConversionUtil.isPrimitiveAndNotNull(operands[1].getType())) {
expectedType = PsiPrimitiveType.getUnboxedType(wrappedExpressionType);
}
else {
expectedType = TypeUtils.getObjectType(wrappedExpression);
}
}
else if (operands[1] == wrappedExpression) {
if (TypeConversionUtil.isPrimitiveAndNotNull(operands[0].getType())) {
expectedType = PsiPrimitiveType.getUnboxedType(wrappedExpressionType);
}
else {
expectedType = TypeUtils.getObjectType(wrappedExpression);
}
}
else {
expectedType = PsiPrimitiveType.getUnboxedType(wrappedExpressionType);
}
}
else if (ComparisonUtils.isComparisonOperation(tokenType)) {
if (operands.length != 2) {
expectedType = null;
return;
}
else if (!TypeConversionUtil.isPrimitiveAndNotNull(wrappedExpressionType)) {
if (PsiPrimitiveType.getUnboxedType(wrappedExpressionType) == null) {
return;
}
}
expectedType = TypeConversionUtil.binaryNumericPromotion(operands[0].getType(), operands[1].getType());
}
else {
expectedType = null;
}
}
/**
* JLS 5.6.1 Unary Numeric Promotion
*/
private static PsiType unaryNumericPromotion(PsiType type) {
if (type == null) {
return null;
}
if (type.equalsToText("java.lang.Byte") || type.equalsToText("java.lang.Short") ||
type.equalsToText("java.lang.Character") || type.equalsToText("java.lang.Integer") ||
type.equals(PsiType.BYTE) || type.equals(PsiType.SHORT) || type.equals(PsiType.CHAR)) {
return PsiType.INT;
}
else if (type.equalsToText("java.lang.Long")) {
return PsiType.LONG;
}
else if (type.equalsToText("java.lang.Float")) {
return PsiType.FLOAT;
}
else if (type.equalsToText("java.lang.Double")) {
return PsiType.DOUBLE;
}
return type;
}
@Override
public void visitPrefixExpression(@NotNull PsiPrefixExpression expression) {
final PsiType type = expression.getType();
if (type instanceof PsiPrimitiveType) {
expectedType = type;
}
else {
expectedType = PsiPrimitiveType.getUnboxedType(type);
}
}
@Override
public void visitPostfixExpression(@NotNull PsiPostfixExpression expression) {
final PsiType type = expression.getType();
if (type instanceof PsiPrimitiveType) {
expectedType = type;
}
else {
expectedType = PsiPrimitiveType.getUnboxedType(type);
}
}
@Override
public void visitSwitchStatement(PsiSwitchStatement statement) {
final PsiExpression expression = statement.getExpression();
if (expression == null) {
return;
}
final PsiType type = expression.getType();
final PsiPrimitiveType unboxedType = PsiPrimitiveType.getUnboxedType(type);
if (unboxedType != null) {
expectedType = unboxedType;
}
else {
expectedType = type;
}
}
@Override
public void visitTypeCastExpression(PsiTypeCastExpression expression) {
if (reportCasts) {
expectedType = expression.getType();
}
}
@Override
public void visitWhileStatement(@NotNull PsiWhileStatement whileStatement) {
expectedType = PsiType.BOOLEAN;
}
@Override
public void visitForStatement(@NotNull PsiForStatement statement) {
expectedType = PsiType.BOOLEAN;
}
@Override
public void visitForeachStatement(PsiForeachStatement statement) {
final PsiExpression iteratedValue = statement.getIteratedValue();
if (iteratedValue == null) {
expectedType = null;
return;
}
final PsiType iteratedValueType = iteratedValue.getType();
if (!(iteratedValueType instanceof PsiClassType)) {
expectedType = null;
return;
}
final PsiClassType classType = (PsiClassType)iteratedValueType;
final PsiType[] parameters = classType.getParameters();
final PsiClass iterableClass = ClassUtils.findClass(CommonClassNames.JAVA_LANG_ITERABLE, statement);
if (iterableClass == null) {
expectedType = null;
}
else {
expectedType = JavaPsiFacade.getElementFactory(statement.getProject()).createType(iterableClass, parameters);
}
}
@Override
public void visitIfStatement(@NotNull PsiIfStatement statement) {
expectedType = PsiType.BOOLEAN;
}
@Override
public void visitDoWhileStatement(@NotNull PsiDoWhileStatement statement) {
expectedType = PsiType.BOOLEAN;
}
@Override
public void visitSynchronizedStatement(@NotNull PsiSynchronizedStatement statement) {
expectedType = TypeUtils.getObjectType(statement);
}
@Override
public void visitAssignmentExpression(@NotNull PsiAssignmentExpression assignment) {
final PsiExpression rExpression = assignment.getRExpression();
final IElementType tokenType = assignment.getOperationTokenType();
final PsiExpression lExpression = assignment.getLExpression();
final PsiType lType = lExpression.getType();
if (rExpression != null && wrappedExpression.equals(rExpression)) {
if (lType == null) {
expectedType = null;
}
else if (TypeUtils.isJavaLangString(lType)) {
if (JavaTokenType.PLUSEQ.equals(tokenType)) {
// e.g. String += any type
expectedType = rExpression.getType();
}
else {
expectedType = lType;
}
}
else if (isOperatorAssignmentOperation(tokenType)) {
if (lType instanceof PsiPrimitiveType) {
expectedType = lType;
}
else {
expectedType = PsiPrimitiveType.getUnboxedType(lType);
}
}
else {
expectedType = lType;
}
}
else {
if (isOperatorAssignmentOperation(tokenType) && !(lType instanceof PsiPrimitiveType)) {
expectedType = PsiPrimitiveType.getUnboxedType(lType);
}
else {
expectedType = lType;
}
}
}
@Override
public void visitConditionalExpression(PsiConditionalExpression conditional) {
final PsiExpression condition = conditional.getCondition();
if (condition.equals(wrappedExpression)) {
expectedType = PsiType.BOOLEAN;
}
else {
expectedType = conditional.getType();
}
}
@Override
public void visitReturnStatement(@NotNull PsiReturnStatement returnStatement) {
final PsiMethod method = PsiTreeUtil.getParentOfType(returnStatement, PsiMethod.class);
if (method != null) {
expectedType = method.getReturnType();
}
}
@Override
public void visitInstanceOfExpression(PsiInstanceOfExpression expression) {
expectedType = TypeUtils.getObjectType(expression);
}
@Override
public void visitDeclarationStatement(PsiDeclarationStatement declaration) {
final PsiElement[] declaredElements = declaration.getDeclaredElements();
for (PsiElement declaredElement : declaredElements) {
if (declaredElement instanceof PsiVariable) {
final PsiVariable variable = (PsiVariable)declaredElement;
final PsiExpression initializer = variable.getInitializer();
if (wrappedExpression.equals(initializer)) {
expectedType = variable.getType();
return;
}
}
}
}
@Override
public void visitExpressionList(PsiExpressionList expressionList) {
final JavaResolveResult result = findCalledMethod(expressionList);
final PsiMethod method = (PsiMethod)result.getElement();
if (method == null) {
expectedType = null;
}
else {
final int parameterPosition = getParameterPosition(expressionList, wrappedExpression);
expectedType = getTypeOfParameter(result, parameterPosition);
}
}
@Override
public void visitNewExpression(PsiNewExpression expression) {
final PsiExpression[] arrayDimensions = expression.getArrayDimensions();
for (PsiExpression arrayDimension : arrayDimensions) {
if (wrappedExpression.equals(arrayDimension)) {
expectedType = PsiType.INT;
}
}
}
@NotNull
private static JavaResolveResult findCalledMethod(PsiExpressionList expressionList) {
final PsiElement parent = expressionList.getParent();
if (parent instanceof PsiCallExpression) {
final PsiCallExpression call = (PsiCallExpression)parent;
return call.resolveMethodGenerics();
}
else if (parent instanceof PsiAnonymousClass) {
final PsiElement grandParent = parent.getParent();
if (grandParent instanceof PsiCallExpression) {
final PsiCallExpression callExpression = (PsiCallExpression)grandParent;
return callExpression.resolveMethodGenerics();
}
}
return JavaResolveResult.EMPTY;
}
@Override
public void visitReferenceExpression(@NotNull PsiReferenceExpression referenceExpression) {
if (calculateTypeForComplexReferences) {
final Project project = referenceExpression.getProject();
final JavaResolveResult resolveResult = referenceExpression.advancedResolve(false);
final PsiElement element = resolveResult.getElement();
PsiSubstitutor substitutor = resolveResult.getSubstitutor();
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
if (element instanceof PsiField) {
final PsiField field = (PsiField)element;
if (!isAccessibleFrom(field, referenceExpression)) {
return;
}
final PsiClass aClass = field.getContainingClass();
if (aClass == null) {
return;
}
final PsiElementFactory factory = psiFacade.getElementFactory();
expectedType = factory.createType(aClass, substitutor);
}
else if (element instanceof PsiMethod) {
final PsiElement parent = referenceExpression.getParent();
final PsiType returnType;
if (parent instanceof PsiMethodCallExpression) {
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)parent;
final PsiType type = methodCallExpression.getType();
if (!PsiType.VOID.equals(type)) {
returnType = findExpectedType(methodCallExpression, true);
}
else {
returnType = null;
}
}
else {
returnType = null;
}
final PsiMethod method = (PsiMethod)element;
final PsiMethod superMethod = findDeepestVisibleSuperMethod(method, returnType, referenceExpression);
final PsiClass aClass;
if (superMethod != null) {
aClass = superMethod.getContainingClass();
if (aClass == null) {
return;
}
substitutor = TypeConversionUtil.getSuperClassSubstitutor(aClass, method.getContainingClass(), substitutor);
}
else {
aClass = method.getContainingClass();
if (aClass == null) {
return;
}
}
final PsiElementFactory factory = psiFacade.getElementFactory();
expectedType = factory.createType(aClass, substitutor);
}
else {
expectedType = null;
}
}
}
@Nullable
private static PsiMethod findDeepestVisibleSuperMethod(PsiMethod method, PsiType returnType, PsiElement element) {
if (method.isConstructor()) {
return null;
}
if (method.hasModifierProperty(PsiModifier.STATIC)) {
return null;
}
if (method.hasModifierProperty(PsiModifier.PRIVATE)) {
return null;
}
final PsiClass aClass = method.getContainingClass();
if (aClass == null) {
return null;
}
final PsiMethod[] superMethods = aClass.findMethodsBySignature(method, true);
PsiMethod topSuper = null;
PsiClass topSuperContainingClass = null;
for (PsiMethod superMethod : superMethods) {
final PsiClass superClass = superMethod.getContainingClass();
if (superClass == null) {
continue;
}
if (aClass.equals(superClass)) {
continue;
}
if (!isAccessibleFrom(superMethod, element)) {
continue;
}
if (returnType != null) {
final PsiType superReturnType = superMethod.getReturnType();
if (superReturnType == null) {
continue;
}
if (!returnType.isAssignableFrom(superReturnType)) {
continue;
}
}
if (topSuper != null && superClass.isInheritor(topSuperContainingClass, true)) {
continue;
}
topSuper = superMethod;
topSuperContainingClass = superClass;
}
return topSuper;
}
private static boolean isAccessibleFrom(PsiMember member, PsiElement referencingLocation) {
if (member.hasModifierProperty(PsiModifier.PUBLIC)) {
return true;
}
final PsiClass containingClass = member.getContainingClass();
if (containingClass == null) {
return false;
}
final PsiClass referencingClass = ClassUtils.getContainingClass(referencingLocation);
if (referencingClass == null) {
return false;
}
if (referencingClass.equals(containingClass)) {
return true;
}
if (member.hasModifierProperty(PsiModifier.PRIVATE)) {
return false;
}
return ClassUtils.inSamePackage(containingClass, referencingLocation);
}
private static boolean isArithmeticOperation(@NotNull IElementType sign) {
return arithmeticOps.contains(sign);
}
private static boolean isBooleanOperation(@NotNull IElementType sign) {
return booleanOps.contains(sign);
}
private static boolean isShiftOperation(@NotNull IElementType sign) {
return shiftOps.contains(sign);
}
private static boolean isOperatorAssignmentOperation(@NotNull IElementType sign) {
return operatorAssignmentOps.contains(sign);
}
private static int getParameterPosition(@NotNull PsiExpressionList expressionList, PsiExpression expression) {
final PsiExpression[] expressions = expressionList.getExpressions();
for (int i = 0; i < expressions.length; i++) {
if (expressions[i].equals(expression)) {
return i;
}
}
return -1;
}
@Nullable
private static PsiType getTypeOfParameter(@NotNull JavaResolveResult result, int parameterPosition) {
final PsiMethod method = (PsiMethod)result.getElement();
if (method == null) {
return null;
}
final PsiSubstitutor substitutor = result.getSubstitutor();
final PsiParameterList parameterList = method.getParameterList();
if (parameterPosition < 0) {
return null;
}
final int parametersCount = parameterList.getParametersCount();
final PsiParameter[] parameters;
if (parameterPosition >= parametersCount) {
final int lastParameterPosition = parametersCount - 1;
if (lastParameterPosition < 0) {
return null;
}
parameters = parameterList.getParameters();
final PsiParameter lastParameter = parameters[lastParameterPosition];
if (lastParameter.isVarArgs()) {
final PsiArrayType arrayType = (PsiArrayType)lastParameter.getType();
return substitutor.substitute(arrayType.getComponentType());
}
return null;
}
parameters = parameterList.getParameters();
final PsiParameter parameter = parameters[parameterPosition];
final PsiType parameterType = parameter.getType();
if (parameter.isVarArgs()) {
final PsiArrayType arrayType = (PsiArrayType)parameterType;
return substitutor.substitute(arrayType.getComponentType());
}
final PsiType type = substitutor.substitute(parameterType);
final TypeStringCreator typeStringCreator = new TypeStringCreator();
type.accept(typeStringCreator);
if (typeStringCreator.isModified()) {
final PsiManager manager = method.getManager();
final Project project = manager.getProject();
final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
try {
final String typeString = typeStringCreator.getTypeString();
return factory.createTypeFromText(typeString, method);
}
catch (IncorrectOperationException e) {
throw new AssertionError("incorrect type string generated from " + type + ": " + e.getMessage());
}
}
return type;
}
/**
* Creates a new type string without any wildcards with final
* extends bounds from the visited type.
*/
private static class TypeStringCreator extends PsiTypeVisitor<Object> {
private final StringBuilder typeString = new StringBuilder();
private boolean modified = false;
@Override
public Object visitType(PsiType type) {
typeString.append(type.getCanonicalText());
return super.visitType(type);
}
@Override
public Object visitWildcardType(PsiWildcardType wildcardType) {
if (wildcardType.isExtends()) {
final PsiType extendsBound = wildcardType.getExtendsBound();
if (extendsBound instanceof PsiClassType) {
final PsiClassType classType = (PsiClassType)extendsBound;
final PsiClass aClass = classType.resolve();
if (aClass != null && aClass.hasModifierProperty(PsiModifier.FINAL)) {
modified = true;
return super.visitClassType(classType);
}
}
}
return super.visitWildcardType(wildcardType);
}
@Override
public Object visitClassType(PsiClassType classType) {
final PsiClassType rawType = classType.rawType();
typeString.append(rawType.getCanonicalText());
final PsiType[] parameterTypes = classType.getParameters();
if (parameterTypes.length > 0) {
typeString.append('<');
final PsiType parameterType1 = parameterTypes[0];
// IDEADEV-25549 says this can be null
if (parameterType1 != null) {
parameterType1.accept(this);
}
for (int i = 1; i < parameterTypes.length; i++) {
typeString.append(',');
final PsiType parameterType = parameterTypes[i];
// IDEADEV-25549 again
if (parameterType != null) {
parameterType.accept(this);
}
}
typeString.append('>');
}
return null;
}
public String getTypeString() {
return typeString.toString();
}
public boolean isModified() {
return modified;
}
}
}
}