blob: 1435dc1f22714f14f8fa16b5931aa6398c877ba4 [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.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiImplUtil;
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 com.intellij.util.Function;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Map;
/**
* @author db
* Date: 27.06.2003
*/
public class TypeEvaluator {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.typeMigration.TypeEvaluator");
private final HashMap<TypeMigrationUsageInfo, LinkedList<PsiType>> myTypeMap;
private final TypeMigrationRules myRules;
private final TypeMigrationLabeler myLabeler;
public TypeEvaluator(final LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> types, final TypeMigrationLabeler labeler) {
myLabeler = labeler;
myRules = labeler.getRules();
myTypeMap = new HashMap<TypeMigrationUsageInfo, LinkedList<PsiType>>();
for (final Pair<TypeMigrationUsageInfo, PsiType> p : types) {
final LinkedList<PsiType> e = new LinkedList<PsiType>();
e.addFirst(p.getSecond());
myTypeMap.put(p.getFirst(), e);
}
}
public boolean setType(final TypeMigrationUsageInfo usageInfo, @NotNull PsiType type) {
final LinkedList<PsiType> t = myTypeMap.get(usageInfo);
final PsiElement element = usageInfo.getElement();
if (type instanceof PsiEllipsisType && !(element instanceof PsiParameter && ((PsiParameter)element).getDeclarationScope() instanceof PsiMethod)) {
type = ((PsiEllipsisType)type).toArrayType();
}
if (t != null) {
if (!t.getFirst().equals(type)) {
if (element instanceof PsiVariable || element instanceof PsiMethod) {
return false;
}
t.addFirst(type);
return true;
}
}
else {
final LinkedList<PsiType> e = new LinkedList<PsiType>();
e.addFirst(type);
myTypeMap.put(usageInfo, e);
return true;
}
return false;
}
@Nullable
public PsiType getType(PsiElement element) {
for (Map.Entry<TypeMigrationUsageInfo, LinkedList<PsiType>> entry : myTypeMap.entrySet()) {
if (Comparing.equal(element, entry.getKey().getElement())) {
return entry.getValue().getFirst();
}
}
if (element.getTextRange() == null) return null;
return getType(new TypeMigrationUsageInfo(element));
}
@Nullable
public PsiType getType(final TypeMigrationUsageInfo usageInfo) {
final LinkedList<PsiType> e = myTypeMap.get(usageInfo);
if (e != null) {
return e.getFirst();
}
return TypeMigrationLabeler.getElementType(usageInfo.getElement());
}
@Nullable
public PsiType evaluateType(final PsiExpression expr) {
if (expr == null) return null;
final LinkedList<PsiType> e = myTypeMap.get(new TypeMigrationUsageInfo(expr));
if (e != null) {
return e.getFirst();
}
if (expr instanceof PsiArrayAccessExpression) {
final PsiType at = evaluateType(((PsiArrayAccessExpression)expr).getArrayExpression());
if (at instanceof PsiArrayType) {
return ((PsiArrayType)at).getComponentType();
}
}
else if (expr instanceof PsiAssignmentExpression) {
return evaluateType(((PsiAssignmentExpression)expr).getLExpression());
}
else if (expr instanceof PsiMethodCallExpression) {
final PsiMethodCallExpression call = (PsiMethodCallExpression)expr;
final JavaResolveResult resolveResult = call.resolveMethodGenerics();
final PsiMethod method = (PsiMethod)resolveResult.getElement();
if (method != null) {
final PsiParameter[] parameters = method.getParameterList().getParameters();
final PsiExpression[] actualParms = call.getArgumentList().getExpressions();
return PsiImplUtil.normalizeWildcardTypeByPosition(createMethodSubstitution(parameters, actualParms, method, call, resolveResult.getSubstitutor(), false).substitute(evaluateType(call.getMethodExpression())), expr);
}
}
else if (expr instanceof PsiPolyadicExpression) {
final PsiExpression[] operands = ((PsiPolyadicExpression)expr).getOperands();
final IElementType sign = ((PsiPolyadicExpression)expr).getOperationTokenType();
PsiType lType = operands.length > 0 ? evaluateType(operands[0]) : null;
for (int i = 1; i < operands.length; i++) {
lType = TypeConversionUtil.calcTypeForBinaryExpression(lType, evaluateType(operands[i]), sign, true);
}
return lType;
}
else if (expr instanceof PsiPostfixExpression) {
return evaluateType(((PsiPostfixExpression)expr).getOperand());
}
else if (expr instanceof PsiPrefixExpression) {
return evaluateType(((PsiPrefixExpression)expr).getOperand());
}
else if (expr instanceof PsiParenthesizedExpression) {
return evaluateType(((PsiParenthesizedExpression)expr).getExpression());
}
else if (expr instanceof PsiConditionalExpression) {
final PsiExpression thenExpression = ((PsiConditionalExpression)expr).getThenExpression();
final PsiExpression elseExpression = ((PsiConditionalExpression)expr).getElseExpression();
final PsiType thenType = evaluateType(thenExpression);
final PsiType elseType = evaluateType(elseExpression);
switch ((thenType == null ? 0 : 1) + (elseType == null ? 0 : 2)) {
case 0:
return expr.getType();
case 1:
return thenType;
case 2:
return elseType;
case 3:
if (TypeConversionUtil.areTypesConvertible(thenType, elseType)) {
return thenType;
}
else if (TypeConversionUtil.areTypesConvertible(elseType, thenType)) {
return elseType;
} else {
switch ((thenType.equals(thenExpression.getType()) ? 0 : 1) + (elseType.equals(elseExpression.getType()) ? 0 : 2)) {
case 0:
return expr.getType();
case 1:
return thenType;
case 2:
return elseType;
case 3:
return expr.getType();
default:
LOG.error("Must not happen.");
return null;
}
}
default:
LOG.error("Must not happen.");
}
}
else if (expr instanceof PsiNewExpression) {
final PsiExpression qualifier = ((PsiNewExpression)expr).getQualifier();
if (qualifier != null) {
final PsiClassType.ClassResolveResult qualifierResult = resolveType(evaluateType(qualifier));
if (qualifierResult.getElement() != null) {
final PsiSubstitutor qualifierSubs = qualifierResult.getSubstitutor();
final PsiClassType.ClassResolveResult result = resolveType(expr.getType());
if (result.getElement() != null) {
final PsiClass aClass = result.getElement();
return JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory()
.createType(aClass, result.getSubstitutor().putAll(qualifierSubs));
}
}
}
}
else if (expr instanceof PsiReferenceExpression) {
final PsiType type = evaluateReferenceExpressionType(expr);
if (type != null) {
return PsiImplUtil.normalizeWildcardTypeByPosition(type, expr);
}
} else if (expr instanceof PsiSuperExpression) {
final PsiClass psiClass = PsiTreeUtil.getParentOfType(expr, PsiClass.class);
if (psiClass != null) {
final PsiClass superClass = psiClass.getSuperClass();
if (superClass != null) {
return getType(new TypeMigrationUsageInfo(superClass));
}
}
}
return getType(expr);
}
@Nullable
private PsiType evaluateReferenceExpressionType(PsiExpression expr) {
final PsiReferenceExpression ref = (PsiReferenceExpression)expr;
final PsiExpression qualifier = ref.getQualifierExpression();
if (qualifier == null) {
final PsiElement resolvee = ref.resolve();
if (resolvee == null) {
return null;
}
return resolvee instanceof PsiClass ? JavaPsiFacade.getElementFactory(resolvee.getProject()).createType((PsiClass)resolvee, PsiSubstitutor.EMPTY) : getType(resolvee);
}
else {
final PsiType qualifierType = evaluateType(qualifier);
if (!(qualifierType instanceof PsiArrayType)) {
final PsiElement element = ref.resolve();
final PsiClassType.ClassResolveResult result = resolveType(qualifierType);
final PsiClass aClass = result.getElement();
if (aClass != null) {
final PsiSubstitutor aSubst = result.getSubstitutor();
if (element instanceof PsiField) {
final PsiField field = (PsiField)element;
PsiType aType = field.getType();
final PsiClass superClass = field.getContainingClass();
if (InheritanceUtil.isInheritorOrSelf(aClass, superClass, true)) {
aType = TypeConversionUtil.getSuperClassSubstitutor(superClass, aClass, PsiSubstitutor.EMPTY).substitute(aType);
}
return aSubst.substitute(aType);
} else if (element instanceof PsiMethod){
final PsiMethod method = (PsiMethod)element;
PsiType aType = method.getReturnType();
final PsiClass superClass = method.getContainingClass();
if (InheritanceUtil.isInheritorOrSelf(aClass, superClass, true)) {
aType = TypeConversionUtil.getSuperClassSubstitutor(superClass, aClass, PsiSubstitutor.EMPTY).substitute(aType);
} else if (InheritanceUtil.isInheritorOrSelf(superClass, aClass, true)) {
final PsiMethod[] methods = method.findSuperMethods(aClass);
if (methods.length > 0) {
aType = methods[0].getReturnType();
}
/*}
final Pair<PsiType, PsiType> pair = myRules.bindTypeParameters(qualifier.getType(), qualifierType, method, ref, myLabeler);
if (pair != null) {
final PsiClass psiClass = PsiUtil.resolveClassInType(aType);
if (psiClass instanceof PsiTypeParameter) {
aSubst = aSubst.put((PsiTypeParameter)psiClass, pair.getSecond());
}*/
}
return aSubst.substitute(aType);
}
}
}
}
return null;
}
public static PsiClassType.ClassResolveResult resolveType(PsiType type) {
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(type);
final PsiClass aClass = resolveResult.getElement();
if (aClass instanceof PsiAnonymousClass) {
final PsiClassType baseClassType = ((PsiAnonymousClass)aClass).getBaseClassType();
return resolveType(resolveResult.getSubstitutor().substitute(baseClassType));
}
return resolveResult;
}
public PsiSubstitutor createMethodSubstitution(final PsiParameter[] parameters, final PsiExpression[] actualParms, final PsiMethod method,
final PsiExpression call) {
return createMethodSubstitution(parameters, actualParms, method, call, PsiSubstitutor.EMPTY, false);
}
public PsiSubstitutor createMethodSubstitution(final PsiParameter[] parameters,
final PsiExpression[] actualParms,
final PsiMethod method,
final PsiExpression call,
PsiSubstitutor subst,
boolean preferSubst) {
final SubstitutorBuilder substitutorBuilder = new SubstitutorBuilder(method, call, subst);
for (int i = 0; i < Math.min(parameters.length, actualParms.length); i++) {
substitutorBuilder.bindTypeParameters(getType(parameters[i]), evaluateType(actualParms[i]));
}
return substitutorBuilder.createSubstitutor(preferSubst);
}
public String getReport() {
final StringBuffer buffer = new StringBuffer();
final String[] t = new String[myTypeMap.size()];
int k = 0;
for (final TypeMigrationUsageInfo info : myTypeMap.keySet()) {
final LinkedList<PsiType> types = myTypeMap.get(info);
final StringBuffer b = new StringBuffer();
if (types != null) {
b.append(info.getElement()).append(" : ");
b.append(StringUtil.join(types, new Function<PsiType, String>() {
public String fun(final PsiType psiType) {
return psiType.getCanonicalText();
}
}, " "));
b.append("\n");
}
t[k++] = b.toString();
}
Arrays.sort(t);
for (String aT : t) {
buffer.append(aT);
}
return buffer.toString();
}
public LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> getMigratedDeclarations() {
final LinkedList<Pair<TypeMigrationUsageInfo, PsiType>> list = new LinkedList<Pair<TypeMigrationUsageInfo, PsiType>>();
for (final TypeMigrationUsageInfo usageInfo : myTypeMap.keySet()) {
final LinkedList<PsiType> types = myTypeMap.get(usageInfo);
final PsiElement element = usageInfo.getElement();
if (element instanceof PsiVariable || element instanceof PsiMethod) {
list.addLast(Pair.create(usageInfo, types.getFirst()));
}
}
return list;
}
@Nullable
static PsiType substituteType(final PsiType migrationType, final PsiType originalType, boolean captureWildcard, PsiClass originalClass, final PsiType rawTypeToReplace) {
if (originalClass != null) {
if (((PsiClassType)originalType).hasParameters() && ((PsiClassType)migrationType).hasParameters()) {
final PsiResolveHelper psiResolveHelper = JavaPsiFacade.getInstance(originalClass.getProject()).getResolveHelper();
final PsiType rawOriginalType = JavaPsiFacade.getElementFactory(originalClass.getProject()).createType(originalClass, PsiSubstitutor.EMPTY);
PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
for (PsiTypeParameter parameter : originalClass.getTypeParameters()) {
final PsiType type = psiResolveHelper.getSubstitutionForTypeParameter(parameter, rawOriginalType, migrationType, false, PsiUtil.getLanguageLevel(originalClass));
if (type != null) {
substitutor = substitutor.put(parameter, captureWildcard && type instanceof PsiWildcardType ? ((PsiWildcardType)type).getExtendsBound() : type);
} else {
return null;
}
}
return substitutor.substitute(rawTypeToReplace);
} else {
return originalType;
}
}
return null;
}
public static PsiType substituteType(final PsiType migrationTtype, final PsiType originalType, final boolean isContraVariantPosition) {
if ( originalType instanceof PsiClassType && migrationTtype instanceof PsiClassType) {
final PsiClass originalClass = ((PsiClassType)originalType).resolve();
if (originalClass != null) {
if (isContraVariantPosition && TypeConversionUtil.erasure(originalType).isAssignableFrom(TypeConversionUtil.erasure(migrationTtype))) {
final PsiClass psiClass = ((PsiClassType)migrationTtype).resolve();
final PsiSubstitutor substitutor = psiClass != null ? TypeConversionUtil.getClassSubstitutor(originalClass, psiClass, PsiSubstitutor.EMPTY) : null;
if (substitutor != null) {
final PsiType psiType =
substituteType(migrationTtype, originalType, false, psiClass, JavaPsiFacade.getElementFactory(psiClass.getProject()).createType(originalClass, substitutor));
if (psiType != null) {
return psiType;
}
}
}
else if (!isContraVariantPosition && TypeConversionUtil.erasure(migrationTtype).isAssignableFrom(TypeConversionUtil.erasure(originalType))) {
final PsiType psiType = substituteType(migrationTtype, originalType, false, originalClass, JavaPsiFacade.getElementFactory(originalClass.getProject()).createType(originalClass, PsiSubstitutor.EMPTY));
if (psiType != null) {
return psiType;
}
}
}
}
return migrationTtype;
}
private class SubstitutorBuilder {
private final Map<PsiTypeParameter, PsiType> myMapping;
private final PsiMethod myMethod;
private final PsiExpression myCall;
private final PsiSubstitutor mySubst;
public SubstitutorBuilder(PsiMethod method, PsiExpression call, PsiSubstitutor subst) {
mySubst = subst;
myMapping = new HashMap<PsiTypeParameter, PsiType>();
myMethod = method;
myCall = call;
}
private void update(final PsiTypeParameter p, PsiType t) {
if (t instanceof PsiPrimitiveType) {
t = ((PsiPrimitiveType)t).getBoxedType(myMethod);
}
final PsiType binding = myMapping.get(p);
if (binding == null) {
myMapping.put(p, t);
}
else if (t != null) {
myMapping.put(p, PsiIntersectionType.createIntersection(binding, t));
}
}
void bindTypeParameters(PsiType formal, final PsiType actual) {
if (formal instanceof PsiWildcardType) formal = ((PsiWildcardType)formal).getBound();
if (formal instanceof PsiArrayType && actual instanceof PsiArrayType) {
bindTypeParameters(((PsiArrayType)formal).getComponentType(), ((PsiArrayType)actual).getComponentType());
return;
}
final Pair<PsiType, PsiType> typePair = myRules.bindTypeParameters(formal, actual, myMethod, myCall, myLabeler);
if (typePair != null) {
bindTypeParameters(typePair.getFirst(), typePair.getSecond());
return;
}
final PsiClassType.ClassResolveResult resultF = resolveType(formal);
final PsiClass classF = resultF.getElement();
if (classF != null) {
if (classF instanceof PsiTypeParameter) {
update((PsiTypeParameter)classF, actual);
return;
}
final PsiClassType.ClassResolveResult resultA = resolveType(actual);
final PsiClass classA = resultA.getElement();
if (classA == null) {
return;
}
if (!classA.equals(classF)) {
final PsiSubstitutor superClassSubstitutor =
TypeConversionUtil.getClassSubstitutor(classF, classA, resultA.getSubstitutor());
if (superClassSubstitutor != null) {
final PsiType aligned = JavaPsiFacade.getInstance(classF.getProject()).getElementFactory().createType(classF, superClassSubstitutor);
bindTypeParameters(formal, aligned);
}
}
final PsiTypeParameter[] typeParms = classA.getTypeParameters();
final PsiSubstitutor substA = resultA.getSubstitutor();
final PsiSubstitutor substF = resultF.getSubstitutor();
for (PsiTypeParameter typeParm : typeParms) {
bindTypeParameters(substF.substitute(typeParm), substA.substitute(typeParm));
}
}
}
public PsiSubstitutor createSubstitutor(boolean preferSubst) {
PsiSubstitutor theSubst = mySubst;
if (preferSubst) {
myMapping.keySet().removeAll(mySubst.getSubstitutionMap().keySet());
}
for (final PsiTypeParameter parm : myMapping.keySet()) {
theSubst = theSubst.put(parm, myMapping.get(parm));
}
return theSubst;
}
}
}