new inference: show bounds incompatibility error messages detected during inference (IDEA-131882)

This commit is contained in:
Anna Kozlova
2015-02-19 18:07:57 +01:00
parent d631901d7e
commit 4f0ab6f093
3 changed files with 121 additions and 43 deletions
@@ -35,6 +35,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiSuperMethodImplUtil;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.util.*;
@@ -207,7 +208,8 @@ public class HighlightMethodUtil {
@NotNull String detailMessage,
@NotNull TextRange textRange) {
String description = MessageFormat.format("{0}; {1}", createClashMethodMessage(method, superMethod, true), detailMessage);
HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create();
HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(
description).create();
QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createMethodReturnFix(method, substitutedSuperReturnType, false));
QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createSuperMethodReturnFix(superMethod, returnType));
@@ -690,7 +692,7 @@ public class HighlightMethodUtil {
QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, action);
}
QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createReplaceAddAllArrayToCollectionFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createSurroundWithArrayFix(methodCall,null));
QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createSurroundWithArrayFix(methodCall, null));
QualifyThisArgumentFix.registerQuickFixAction(methodCandidates, methodCall, highlightInfo, fixRange);
CandidateInfo[] candidates = resolveHelper.getReferencedMethodCandidates(methodCall, true);
@@ -764,12 +766,14 @@ public class HighlightMethodUtil {
@Language("HTML")
@NonNls String parensizedName = methodName + (parameters.length == 0 ? "( ) " : "");
final String errorMessage = InferenceSession.getInferenceErrorMessage(list.getParent());
return JavaErrorMessages.message(
"argument.mismatch.html.tooltip",
Integer.valueOf(cols - parameters.length + 1), parensizedName,
HighlightUtil.formatClass(aClass, false),
createMismatchedArgsHtmlTooltipParamsRow(parameters, substitutor, expressions),
createMismatchedArgsHtmlTooltipArgumentsRow(expressions, parameters, substitutor, cols)
createMismatchedArgsHtmlTooltipArgumentsRow(expressions, parameters, substitutor, cols),
errorMessage != null ? "<br/>reason: " + XmlStringUtil.escapeString(errorMessage).replaceAll("\n", "<br/>") : ""
);
}
@@ -870,7 +874,13 @@ public class HighlightMethodUtil {
s += "</tr>";
}
s+= "</table></body></html>";
s+= "</table>";
final String errorMessage = InferenceSession.getInferenceErrorMessage(list.getParent());
if (errorMessage != null) {
s+= "reason: ";
s += XmlStringUtil.escapeString(errorMessage).replaceAll("\n", "<br/>");
}
s+= "</body></html>";
return s;
}
@@ -52,6 +52,11 @@ public class InferenceSession {
}
};
private static final Key<Map<PsiTypeParameter, String>> INFERENCE_FAILURE_MESSAGE = Key.create("FAILURE_MESSAGE");
private static final String EQUALITY_CONSTRAINTS_PRESENTATION = "equality constraints";
private static final String UPPER_BOUNDS_PRESENTATION = "upper bounds";
private static final String LOWER_BOUNDS_PRESENTATION = "lower bounds";
private final Set<InferenceVariable> myInferenceVariables = new LinkedHashSet<InferenceVariable>();
private final List<ConstraintFormula> myConstraints = new ArrayList<ConstraintFormula>();
private final Set<ConstraintFormula> myConstraintsCopy = new HashSet<ConstraintFormula>();
@@ -428,14 +433,20 @@ public class InferenceSession {
}
private PsiSubstitutor prepareSubstitution() {
for (InferenceVariable inferenceVariable : myInferenceVariables) {
final PsiTypeParameter typeParameter = inferenceVariable.getParameter();
PsiType instantiation = inferenceVariable.getInstantiation();
if (instantiation == PsiType.NULL) {
ArrayList<InferenceVariable> allVars = new ArrayList<InferenceVariable>(myInferenceVariables);
while (!allVars.isEmpty()) {
final List<InferenceVariable> variables = InferenceVariablesOrder.resolveOrder(allVars, this);
for (InferenceVariable inferenceVariable : variables) {
final PsiTypeParameter typeParameter = inferenceVariable.getParameter();
PsiType instantiation = inferenceVariable.getInstantiation();
//failed inference
mySiteSubstitutor = mySiteSubstitutor
.put(typeParameter, JavaPsiFacade.getInstance(typeParameter.getProject()).getElementFactory().createType(typeParameter));
if (instantiation == PsiType.NULL) {
checkBoundsConsistency(mySiteSubstitutor, inferenceVariable);
mySiteSubstitutor = mySiteSubstitutor
.put(typeParameter, JavaPsiFacade.getInstance(typeParameter.getProject()).getElementFactory().createType(typeParameter));
}
}
allVars.removeAll(variables);
}
return mySiteSubstitutor;
}
@@ -864,7 +875,6 @@ public class InferenceSession {
}
private PsiSubstitutor resolveSubset(Collection<InferenceVariable> vars, PsiSubstitutor substitutor) {
nextVar:
for (InferenceVariable var : vars) {
LOG.assertTrue(var.getInstantiation() == PsiType.NULL);
final PsiTypeParameter typeParameter = var.getParameter();
@@ -872,43 +882,100 @@ public class InferenceSession {
continue;//todo
}
final PsiType eqBound = getEqualsBound(var, substitutor);
if (eqBound != PsiType.NULL && eqBound instanceof PsiPrimitiveType) continue;
final PsiType lowerBound = getLowerBound(var, substitutor);
final PsiType upperBound = getUpperBound(var, substitutor);
PsiType type;
if (eqBound != PsiType.NULL && (myErased || eqBound != null)) {
if (lowerBound != PsiType.NULL && !TypeConversionUtil.isAssignable(eqBound, lowerBound)) {
continue;
} else {
type = eqBound;
}
final PsiType type = checkBoundsConsistency(substitutor, var);
if (type != PsiType.NULL) {
substitutor = substitutor.put(typeParameter, type);
}
else {
type = lowerBound;
}
if (type == PsiType.NULL) {
if (var.isThrownBound() && isThrowable(var.getBounds(InferenceBound.UPPER))) {
type = PsiType.getJavaLangRuntimeException(myManager, GlobalSearchScope.allScope(myManager.getProject()));
}
else {
if (substitutor.putAll(mySiteSubstitutor).getSubstitutionMap().get(typeParameter) != null) continue;
type = myErased ? null : upperBound;
}
}
else {
for (PsiType upperType : var.getBounds(InferenceBound.UPPER)) {
if (isProperType(upperType) && !TypeConversionUtil.isAssignable(substitutor.substitute(upperType), lowerBound)) {
continue nextVar;
}
}
}
substitutor = substitutor.put(typeParameter, type);
}
return substitutor;
}
private PsiType checkBoundsConsistency(PsiSubstitutor substitutor, InferenceVariable var) {
final PsiType eqBound = getEqualsBound(var, substitutor);
if (eqBound != PsiType.NULL && eqBound instanceof PsiPrimitiveType) return PsiType.NULL;
final PsiType lowerBound = getLowerBound(var, substitutor);
final PsiType upperBound = getUpperBound(var, substitutor);
PsiType type;
if (eqBound != PsiType.NULL && (myErased || eqBound != null)) {
if (lowerBound != PsiType.NULL && !TypeConversionUtil.isAssignable(eqBound, lowerBound)) {
registerIncompatibleErrorMessage(
incompatibleBoundsMessage(var, substitutor, InferenceBound.EQ, EQUALITY_CONSTRAINTS_PRESENTATION, InferenceBound.LOWER, LOWER_BOUNDS_PRESENTATION),
var.getParameter());
return PsiType.NULL;
} else {
type = eqBound;
}
}
else {
type = lowerBound;
}
if (type == PsiType.NULL) {
if (var.isThrownBound() && isThrowable(var.getBounds(InferenceBound.UPPER))) {
type = PsiType.getJavaLangRuntimeException(myManager, GlobalSearchScope.allScope(myManager.getProject()));
}
else {
if (substitutor.putAll(mySiteSubstitutor).getSubstitutionMap().get(var.getParameter()) != null) return PsiType.NULL;
type = myErased ? null : upperBound;
}
}
else {
for (PsiType upperType : var.getBounds(InferenceBound.UPPER)) {
if (isProperType(upperType) && !TypeConversionUtil.isAssignable(substitutor.substitute(upperType), lowerBound)) {
final String incompatibleBoundsMessage;
if (type != lowerBound) {
incompatibleBoundsMessage = incompatibleBoundsMessage(var, substitutor, InferenceBound.EQ, EQUALITY_CONSTRAINTS_PRESENTATION, InferenceBound.UPPER, UPPER_BOUNDS_PRESENTATION);
}
else {
incompatibleBoundsMessage = incompatibleBoundsMessage(var, substitutor, InferenceBound.LOWER, LOWER_BOUNDS_PRESENTATION, InferenceBound.UPPER, UPPER_BOUNDS_PRESENTATION);
}
registerIncompatibleErrorMessage(incompatibleBoundsMessage, var.getParameter());
return PsiType.NULL;
}
}
}
return type;
}
private void registerIncompatibleErrorMessage(String value, PsiTypeParameter parameter) {
if (myContext != null) {
Map<PsiTypeParameter, String> errorMessage = myContext.getUserData(INFERENCE_FAILURE_MESSAGE);
if (errorMessage == null) {
errorMessage = new LinkedHashMap<PsiTypeParameter, String>();
myContext.putUserData(INFERENCE_FAILURE_MESSAGE, errorMessage);
}
errorMessage.put(parameter, value);
}
}
@Nullable
public static String getInferenceErrorMessage(PsiElement context) {
final Map<PsiTypeParameter, String> errorsMap = context.getUserData(INFERENCE_FAILURE_MESSAGE);
if (errorsMap != null) {
return StringUtil.join(errorsMap.values(), "\n");
}
return null;
}
private String incompatibleBoundsMessage(final InferenceVariable var,
final PsiSubstitutor substitutor,
final InferenceBound lowBound,
final String lowBoundName,
final InferenceBound upperBound,
final String upperBoundName) {
final Function<PsiType, String> typePresentation = new Function<PsiType, String>() {
@Override
public String fun(PsiType type) {
final PsiType substituted = substituteNonProperBound(type, substitutor);
return (substituted != null ? substituted : type).getPresentableText();
}
};
return "inference variable " + var.getName() + " has incompatible bounds:\n " +
lowBoundName + ": " + StringUtil.join(var.getBounds(lowBound), typePresentation, ", ") + "\n" +
upperBoundName + ": " + StringUtil.join(var.getBounds(upperBound), typePresentation, ", ");
}
private PsiType getLowerBound(InferenceVariable var, PsiSubstitutor substitutor) {
return composeBound(var, InferenceBound.LOWER, new Function<Pair<PsiType, PsiType>, PsiType>() {
@Override
@@ -181,7 +181,8 @@ argument.mismatch.html.tooltip=\
<html><body><table border=0>\
<tr><td><b>{1}</b></td>{3}<td colspan={0}>in <b>{2}</b>\\&nbsp;cannot be applied</td></tr>\
<tr><td>to</td>{4}</tr>\
</table></body></html>
</table>\
{5}</body></html>
# {0} - left raw type, {1} - required row, {2} - right raw type, {3} - found row
incompatible.types.html.tooltip=\