blob: f3574536c2eb34e1858ba13b7507b82fafb8c37b [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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.intellij.refactoring.typeMigration;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.typeMigration.usageInfo.TypeMigrationUsageInfo;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.Map;
/**
* @author anna
* Date: 04-Apr-2008
*/
class TypeMigrationStatementProcessor extends JavaRecursiveElementVisitor {
private final PsiElement myStatement;
private final TypeMigrationLabeler myLabeler;
private static final Logger LOG = Logger.getInstance("#" + TypeMigrationStatementProcessor.class.getName());
private final TypeEvaluator myTypeEvaluator;
public TypeMigrationStatementProcessor(final PsiElement expression, TypeMigrationLabeler labeler) {
myStatement = expression;
myLabeler = labeler;
myTypeEvaluator = myLabeler.getTypeEvaluator();
}
@Override
public void visitAssignmentExpression(PsiAssignmentExpression expression) {
super.visitAssignmentExpression(expression);
final PsiExpression lExpression = expression.getLExpression();
final TypeView left = new TypeView(lExpression);
final PsiExpression rExpression = expression.getRExpression();
if (rExpression == null) return;
final TypeView right = new TypeView(rExpression);
final IElementType sign = expression.getOperationTokenType();
final PsiType ltype = left.getType();
final PsiType rtype = right.getType();
if (ltype == null || rtype == null) return;
if (sign != JavaTokenType.EQ) {
final IElementType binaryOperator = TypeConversionUtil.convertEQtoOperation(sign);
if (!TypeConversionUtil.isBinaryOperatorApplicable(binaryOperator, ltype, rtype, false)) {
if (left.isChanged()) {
findConversionOrFail(expression, lExpression, left.getTypePair());
}
if (right.isChanged()) {
findConversionOrFail(expression, rExpression, right.getTypePair());
}
return;
}
}
switch (TypeInfection.getInfection(left, right)) {
case TypeInfection.NONE_INFECTED:
break;
case TypeInfection.LEFT_INFECTED:
myLabeler.migrateExpressionType(rExpression, ltype, myStatement, TypeConversionUtil.isAssignable(ltype, rtype), true);
break;
case TypeInfection.RIGHT_INFECTED:
myLabeler.migrateExpressionType(lExpression, rtype, myStatement, TypeConversionUtil.isAssignable(ltype, rtype), false);
break;
case TypeInfection.BOTH_INFECTED:
addTypeUsage(lExpression);
addTypeUsage(rExpression);
break;
default:
LOG.error("Must not happen.");
}
}
@Override
public void visitArrayAccessExpression(final PsiArrayAccessExpression expression) {
super.visitArrayAccessExpression(expression);
final PsiExpression indexExpression = expression.getIndexExpression();
if (indexExpression != null) {
checkIndexExpression(indexExpression);
}
final TypeView typeView = new TypeView(expression.getArrayExpression());
if (typeView.isChanged() && typeView.getType() instanceof PsiClassType) {
final TypeConversionDescriptorBase conversion =
myLabeler.getRules().findConversion(typeView.getTypePair().first, typeView.getType(), null, expression, false, myLabeler);
if (conversion == null) {
myLabeler.markFailedConversion(typeView.getTypePair(), expression);
}
else {
myLabeler.setConversionMapping(expression, conversion);
myTypeEvaluator.setType(new TypeMigrationUsageInfo(expression), myTypeEvaluator.evaluateType(expression));
}
}
}
@Override
public void visitSwitchLabelStatement(PsiSwitchLabelStatement statement) {
super.visitSwitchLabelStatement(statement);
final PsiExpression caseValue = statement.getCaseValue();
if (caseValue != null) {
final TypeView typeView = new TypeView(caseValue);
if (typeView.isChanged()) {
final PsiSwitchStatement switchStatement = statement.getEnclosingSwitchStatement();
if (switchStatement != null) {
final PsiExpression expression = switchStatement.getExpression();
myLabeler.migrateExpressionType(expression, typeView.getType(), myStatement, false, false);
}
}
}
}
@Override
public void visitInstanceOfExpression(final PsiInstanceOfExpression expression) {
super.visitInstanceOfExpression(expression);
final PsiTypeElement typeElement = expression.getCheckType();
if (typeElement != null) {
final PsiExpression consideredExpression = expression.getOperand();
final PsiType migrationType = myTypeEvaluator.evaluateType(consideredExpression);
final PsiType fixedType = typeElement.getType();
if (migrationType != null && !TypeConversionUtil.isAssignable(migrationType, fixedType)) {
myLabeler.markFailedConversion(Pair.create(fixedType, migrationType), consideredExpression);
}
}
}
@Override
public void visitTypeCastExpression(final PsiTypeCastExpression expression) {
super.visitTypeCastExpression(expression);
final PsiTypeElement typeElement = expression.getCastType();
if (typeElement != null) {
final PsiType fixedType = typeElement.getType();
final PsiType migrationType = myTypeEvaluator.evaluateType(expression.getOperand());
if (migrationType != null && !TypeConversionUtil.areTypesConvertible(migrationType, fixedType)) {
myLabeler.markFailedConversion(Pair.create(fixedType, migrationType), expression);
}
}
}
@Override
public void visitVariable(PsiVariable variable) {
super.visitVariable(variable);
final PsiExpression initializer = variable.getInitializer();
if (initializer != null && initializer.getType() != null) {
processVariable(variable, initializer, null, null, null, false);
}
}
@Override
public void visitReturnStatement(PsiReturnStatement statement) { // has to change method return type corresponding to new value type
super.visitReturnStatement(statement);
final PsiMethod method = PsiTreeUtil.getParentOfType(statement, PsiMethod.class);
final PsiExpression value = statement.getReturnValue();
if (method != null && value != null) {
final PsiType returnType = method.getReturnType();
final PsiType valueType = myTypeEvaluator.evaluateType(value);
if (returnType != null && valueType != null) {
if (!myLabeler.addMigrationRoot(method, valueType, myStatement, TypeConversionUtil.isAssignable(returnType, valueType), true)
&& TypeMigrationLabeler.typeContainsTypeParameters(returnType)) {
myLabeler.markFailedConversion(Pair.create(returnType, valueType), value);
}
}
}
}
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
final PsiExpression qualifierExpression = expression.getQualifierExpression();
if (qualifierExpression != null && qualifierExpression.isPhysical()) {
qualifierExpression.accept(this);
final TypeView qualifierView = new TypeView(qualifierExpression);
if (qualifierView.isChanged()) {
final PsiMember member = (PsiMember)expression.advancedResolve(false).getElement();
if (member == null) return;
final Pair<PsiType, PsiType> typePair = qualifierView.getTypePair();
final TypeConversionDescriptorBase conversion = myLabeler.getRules().findConversion(typePair.getFirst(), typePair.getSecond(), member, expression, false, myLabeler);
if (conversion == null) {
myLabeler.markFailedConversion(typePair, qualifierExpression);
} else {
final PsiElement parent = Util.getEssentialParent(expression);
if (parent instanceof PsiMethodCallExpression) {
myLabeler.setConversionMapping((PsiMethodCallExpression)parent, conversion);
myTypeEvaluator.setType(new TypeMigrationUsageInfo(parent), myTypeEvaluator.evaluateType((PsiExpression)parent));
} else {
myLabeler.setConversionMapping(expression, conversion);
myTypeEvaluator.setType(new TypeMigrationUsageInfo(expression), myTypeEvaluator.evaluateType(expression));
}
}
}
}
}
@Override
public void visitIfStatement(PsiIfStatement statement) {
super.visitIfStatement(statement);
final PsiExpression condition = statement.getCondition();
if (condition != null) {
final TypeView view = new TypeView(condition);
if (view.isChanged()) { //means that boolean condition becomes non-boolean
findConversionOrFail(condition, condition, view.getTypePair());
}
}
}
@Override
public void visitForeachStatement(final PsiForeachStatement statement) {
super.visitForeachStatement(statement);
final PsiExpression value = statement.getIteratedValue();
final PsiParameter psiParameter = statement.getIterationParameter();
if (value != null) {
final TypeView typeView = new TypeView(value);
PsiType psiType = typeView.getType();
if (psiType instanceof PsiArrayType) {
psiType = ((PsiArrayType)psiType).getComponentType();
}
else if (psiType instanceof PsiClassType) {
final PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)psiType).resolveGenerics();
final PsiClass psiClass = resolveResult.getElement();
final PsiType targetTypeParameter = getTargetTypeParameter(psiClass, value, typeView);
if (targetTypeParameter == null) return;
psiType = resolveResult.getSubstitutor().substitute(targetTypeParameter);
if (psiType instanceof PsiWildcardType) {
psiType = ((PsiWildcardType)psiType).getExtendsBound();
}
}
else {
return;
}
final TypeView left = new TypeView(psiParameter, null, null);
if (TypeInfection.getInfection(left, typeView) == TypeInfection.LEFT_INFECTED) {
PsiType iterableType;
final PsiType typeViewType = typeView.getType();
if (typeViewType instanceof PsiArrayType) {
iterableType = left.getType().createArrayType();
} else {
final PsiClass iterableClass = PsiUtil.resolveClassInType(typeViewType);
LOG.assertTrue(iterableClass != null);
final PsiType targetType = getTargetTypeParameter(iterableClass, value, typeView);
final PsiClass typeParam = PsiUtil.resolveClassInClassTypeOnly(targetType);
if (!(typeParam instanceof PsiTypeParameter)) return;
final Map<PsiTypeParameter, PsiType> substMap = Collections.singletonMap(((PsiTypeParameter)typeParam), left.getType());
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(iterableClass.getProject());
iterableType = factory.createType(iterableClass, factory.createSubstitutor(substMap));
}
myLabeler.migrateExpressionType(value, iterableType, myStatement, TypeConversionUtil.isAssignable(iterableType, typeViewType), true);
} else {
processVariable(psiParameter, value, psiType, null, null, false);
}
}
}
private PsiType getTargetTypeParameter(PsiClass iterableClass, PsiExpression value, TypeView typeView) {
final Project project = iterableClass.getProject();
final PsiClass itClass =
JavaPsiFacade.getInstance(project).findClass("java.lang.Iterable", GlobalSearchScope.allScope(project));
if (itClass == null) return null;
if (!InheritanceUtil.isInheritorOrSelf(iterableClass, itClass, true)) {
findConversionOrFail(value, value, typeView.getTypePair());
return null;
}
final PsiSubstitutor aSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(itClass, iterableClass, PsiSubstitutor.EMPTY);
return aSubstitutor.substitute(itClass.getTypeParameters()[0]);
}
@Override
public void visitNewExpression(final PsiNewExpression expression) {
super.visitNewExpression(expression);
final PsiExpression[] dimensions = expression.getArrayDimensions();
for (PsiExpression dimension : dimensions) {
checkIndexExpression(dimension);
}
final PsiArrayInitializerExpression arrayInitializer = expression.getArrayInitializer();
if (arrayInitializer != null) {
processArrayInitializer(arrayInitializer, expression);
}
}
@Override
public void visitArrayInitializerExpression(final PsiArrayInitializerExpression expression) {
super.visitArrayInitializerExpression(expression);
processArrayInitializer(expression, expression);
}
@Override
public void visitPostfixExpression(final PsiPostfixExpression expression) {
super.visitPostfixExpression(expression);
processUnaryExpression(expression, expression.getOperationSign());
}
@Override
public void visitPrefixExpression(final PsiPrefixExpression expression) {
super.visitPrefixExpression(expression);
processUnaryExpression(expression, expression.getOperationSign());
}
private void processUnaryExpression(final PsiExpression expression, PsiJavaToken sign) {
final TypeView typeView = new TypeView(expression);
if (typeView.isChanged()) {
if (!TypeConversionUtil.isUnaryOperatorApplicable(sign, typeView.getType())) {
findConversionOrFail(expression, expression, typeView.getTypePair());
}
}
}
private void findConversionOrFail(PsiExpression expression, PsiExpression toFail, Pair<PsiType, PsiType> typePair) {
final TypeConversionDescriptorBase conversion = myLabeler.getRules().findConversion(typePair.getFirst(), typePair.getSecond(), null, expression, myLabeler);
if (conversion == null) {
myLabeler.markFailedConversion(typePair, toFail);
}
else {
myLabeler.setConversionMapping(expression, conversion);
final PsiType psiType = myTypeEvaluator.evaluateType(expression);
LOG.assertTrue(psiType != null, expression);
myTypeEvaluator.setType(new TypeMigrationUsageInfo(expression), psiType);
}
}
@Override
public void visitPolyadicExpression(PsiPolyadicExpression expression) {
super.visitPolyadicExpression(expression);
final PsiExpression[] operands = expression.getOperands();
if (operands.length == 0) return;
final IElementType operationTokenType = expression.getOperationTokenType();
PsiExpression lOperand = operands[0];
TypeView left = new TypeView(lOperand);
for(int i = 1; i < operands.length; i++) {
final PsiExpression rOperand = operands[i];
if (rOperand == null) return;
final TypeView right = new TypeView(rOperand);
if (!TypeConversionUtil.isBinaryOperatorApplicable(operationTokenType, left.getType(), right.getType(), false)) {
if (left.isChanged()) {
findConversionOrFail(expression, lOperand, left.getTypePair());
}
if (right.isChanged()) {
findConversionOrFail(expression, rOperand, right.getTypePair());
}
}
lOperand = rOperand;
left = right;
}
}
private void processArrayInitializer(final PsiArrayInitializerExpression expression, final PsiExpression parentExpression) {
final PsiExpression[] initializers = expression.getInitializers();
PsiType migrationType = null;
for (PsiExpression initializer : initializers) {
final TypeView typeView = new TypeView(initializer);
if (typeView.isChanged()) {
final PsiType type = typeView.getType();
if (migrationType == null || !TypeConversionUtil.isAssignable(migrationType, type)) {
if (migrationType != null && !TypeConversionUtil.isAssignable(type, migrationType)) {
myLabeler.markFailedConversion(Pair.create(parentExpression.getType(), type), parentExpression);
return;
}
migrationType = type;
}
}
}
final PsiType exprType = expression.getType();
if (migrationType != null && exprType instanceof PsiArrayType) {
final boolean alreadyProcessed = TypeConversionUtil.isAssignable(((PsiArrayType)exprType).getComponentType(), migrationType);
myLabeler.migrateExpressionType(parentExpression, alreadyProcessed ? exprType : migrationType.createArrayType(), expression, alreadyProcessed, true);
}
}
private void checkIndexExpression(final PsiExpression indexExpression) {
final PsiType indexType = myTypeEvaluator.evaluateType(indexExpression);
if (indexType != null && !TypeConversionUtil.isAssignable(PsiType.INT, indexType)) {
myLabeler.markFailedConversion(Pair.create(indexExpression.getType(), indexType), indexExpression);
}
}
@Override
public void visitMethodCallExpression(final PsiMethodCallExpression methodCallExpression) {
super.visitMethodCallExpression(methodCallExpression);
final JavaResolveResult resolveResult = methodCallExpression.resolveMethodGenerics();
final PsiElement method = resolveResult.getElement();
if (method instanceof PsiMethod) {
final PsiExpression[] psiExpressions = methodCallExpression.getArgumentList().getExpressions();
final PsiParameter[] originalParams = ((PsiMethod)method).getParameterList().getParameters();
final PsiSubstitutor evalSubstitutor = myTypeEvaluator.createMethodSubstitution(originalParams, psiExpressions, (PsiMethod)method, methodCallExpression);
for (int i = 0; i < psiExpressions.length; i++) {
PsiParameter originalParameter;
if (originalParams.length <= i) {
if (originalParams.length > 0 && originalParams[originalParams.length - 1].isVarArgs()) {
originalParameter = originalParams[originalParams.length - 1];
} else {
continue;
}
}
else {
originalParameter = originalParams[i];
}
processVariable(originalParameter, psiExpressions[i], null, resolveResult.getSubstitutor(), evalSubstitutor, true);
}
final PsiExpression qualifier = methodCallExpression.getMethodExpression().getQualifierExpression();
if (qualifier != null && qualifier.isPhysical() && !new TypeView(qualifier).isChanged()) { //substitute property otherwise
final PsiType qualifierType = qualifier.getType();
if (qualifierType instanceof PsiClassType) {
final PsiClassType.ClassResolveResult classResolveResult = ((PsiClassType)qualifierType).resolveGenerics();
final PsiType migrationType =
classResolveResult.getSubstitutor().substitute(evalSubstitutor.substitute(JavaPsiFacade.getElementFactory(myStatement.getProject()).createType(classResolveResult.getElement(), PsiSubstitutor.EMPTY)));
myLabeler.migrateExpressionType(qualifier, migrationType, myStatement, migrationType.equals(qualifierType), true);
}
}
}
}
private void processVariable(final PsiVariable variable,
final PsiExpression value,
final PsiType migrationType,
final PsiSubstitutor varSubstitutor,
final PsiSubstitutor evalSubstitutor,
final boolean isCovariantPosition) {
final TypeView right = new TypeView(value);
final TypeView left = new TypeView(variable, varSubstitutor, evalSubstitutor);
final PsiType declarationType = left.getType();
switch (TypeInfection.getInfection(left, right)) {
case TypeInfection.NONE_INFECTED:
break;
case TypeInfection.LEFT_INFECTED:
final PsiType valueType = right.getType();
if (valueType != null && declarationType != null) {
myLabeler.migrateExpressionType(value, declarationType, myStatement, TypeConversionUtil.isAssignable(declarationType, valueType), true);
}
break;
case TypeInfection.RIGHT_INFECTED:
PsiType psiType = migrationType != null ? migrationType : right.getType();
if (psiType != null && declarationType != null &&
!myLabeler.addMigrationRoot(variable, psiType, myStatement, TypeConversionUtil.isAssignable(declarationType, psiType), true) &&
!TypeConversionUtil.isAssignable(left.getType(), psiType)) {
myLabeler.convertExpression(value, psiType, left.getType(), isCovariantPosition);
}
break;
case TypeInfection.BOTH_INFECTED:
addTypeUsage(variable);
break;
default:
LOG.error("Must not happen.");
}
}
private void addTypeUsage(final PsiElement typedElement) {
if (typedElement instanceof PsiReferenceExpression) {
myLabeler.setTypeUsage(((PsiReferenceExpression)typedElement).resolve(), myStatement);
}
else if (typedElement instanceof PsiMethodCallExpression) {
myLabeler.setTypeUsage(((PsiMethodCallExpression)typedElement).resolveMethod(), myStatement);
}
else {
myLabeler.setTypeUsage(typedElement, myStatement);
}
}
private class TypeView {
final PsiType myOriginType;
final PsiType myType;
final boolean myChanged;
public TypeView(@NotNull PsiExpression expr) {
PsiType exprType = expr.getType();
exprType = exprType instanceof PsiEllipsisType ? ((PsiEllipsisType)exprType).toArrayType() : exprType;
myOriginType = GenericsUtil.getVariableTypeByExpressionType(exprType);
PsiType type = myTypeEvaluator.evaluateType(expr);
type = type instanceof PsiEllipsisType ? ((PsiEllipsisType)type).toArrayType() : type;
myType = GenericsUtil.getVariableTypeByExpressionType(type);
myChanged = (myOriginType == null || myType == null) ? false : !myType.equals(myOriginType);
}
public TypeView(PsiVariable var, PsiSubstitutor varSubstitutor, PsiSubstitutor evalSubstitutor) {
myOriginType = varSubstitutor != null ? varSubstitutor.substitute(var.getType()) : var.getType();
myType = evalSubstitutor != null
? evalSubstitutor.substitute(myTypeEvaluator.getType(var))
: myTypeEvaluator.getType(var);
myChanged = (myOriginType == null || myType == null) ? false : !myType.equals(myOriginType);
}
public PsiType getType() {
return myType;
}
public boolean isChanged() {
return myChanged;
}
public Pair<PsiType, PsiType> getTypePair() {
return Pair.create(myOriginType, myType);
}
}
private static class TypeInfection {
static final int NONE_INFECTED = 0;
static final int LEFT_INFECTED = 1;
static final int RIGHT_INFECTED = 2;
static final int BOTH_INFECTED = 3;
static int getInfection(final TypeView left, final TypeView right) {
return (left.isChanged() ? 1 : 0) + (right.isChanged() ? 2 : 0);
}
}
}