move inference error from element to method candidate to avoid overload method pollution; show inference error in the editor if applicability check was successful; register inference error if variable has incompatible upper bounds (IDEA-145106)

This commit is contained in:
Anna Kozlova
2015-11-16 10:05:50 +01:00
parent 445f4407fc
commit fcf8e6f373
8 changed files with 151 additions and 95 deletions
@@ -1317,24 +1317,14 @@ public class GenericsHighlightUtil {
static HighlightInfo checkInferredIntersections(PsiSubstitutor substitutor, TextRange ref) {
for (Map.Entry<PsiTypeParameter, PsiType> typeEntry : substitutor.getSubstitutionMap().entrySet()) {
final String parameterName = typeEntry.getKey().getName();
final PsiType type = typeEntry.getValue();
if (type instanceof PsiIntersectionType) {
final PsiType[] conjuncts = ((PsiIntersectionType)type).getConjuncts();
for (int i = 0; i < conjuncts.length; i++) {
PsiClass conjunct = PsiUtil.resolveClassInClassTypeOnly(conjuncts[i]);
if (conjunct != null && !conjunct.isInterface()) {
for (int i1 = i + 1; i1 < conjuncts.length; i1++) {
PsiClass oppositeConjunct = PsiUtil.resolveClassInClassTypeOnly(conjuncts[i1]);
if (oppositeConjunct != null && !oppositeConjunct.isInterface()) {
if (!conjunct.isInheritor(oppositeConjunct, true) && !oppositeConjunct.isInheritor(conjunct, true)) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.descriptionAndTooltip("Type parameter " + typeEntry.getKey().getName() + " has incompatible upper bounds: " +
conjunct.getName() + " and " + oppositeConjunct.getName())
.range(ref).create();
}
}
}
}
final String conflictingConjunctsMessage = ((PsiIntersectionType)type).getConflictingConjunctsMessage();
if (conflictingConjunctsMessage != null) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.descriptionAndTooltip("Type parameter " + parameterName + " has incompatible upper bounds: " + conflictingConjunctsMessage)
.range(ref).create();
}
}
}
@@ -35,7 +35,6 @@ 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.*;
@@ -363,6 +362,13 @@ public class HighlightMethodUtil {
if (highlightInfo == null) {
highlightInfo = checkVarargParameterErasureToBeAccessible((MethodCandidateInfo)resolveResult, methodCall);
}
if (highlightInfo == null && resolveResult instanceof MethodCandidateInfo) {
final String errorMessage = ((MethodCandidateInfo)resolveResult).getInferenceErrorMessage();
if (errorMessage != null) {
highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip(errorMessage).range(fixRange).create();
}
}
}
}
else {
@@ -781,20 +787,21 @@ public class HighlightMethodUtil {
PsiClass aClass = method.getContainingClass();
PsiParameter[] parameters = method.getParameterList().getParameters();
String methodName = method.getName();
return createMismatchedArgumentsHtmlTooltip(list, parameters, methodName, substitutor, aClass);
return createMismatchedArgumentsHtmlTooltip(list, info, parameters, methodName, substitutor, aClass);
}
private static String createShortMismatchedArgumentsHtmlTooltip(PsiExpressionList list,
PsiParameter[] parameters,
String methodName,
PsiSubstitutor substitutor,
PsiClass aClass) {
@Nullable MethodCandidateInfo info,
PsiParameter[] parameters,
String methodName,
PsiSubstitutor substitutor,
PsiClass aClass) {
PsiExpression[] expressions = list.getExpressions();
int cols = Math.max(parameters.length, expressions.length);
@Language("HTML")
@NonNls String parensizedName = methodName + (parameters.length == 0 ? "(&nbsp;)&nbsp;" : "");
final String errorMessage = InferenceSession.getInferenceErrorMessage(list.getParent());
final String errorMessage = info != null ? info.getInferenceErrorMessage() : null;
return JavaErrorMessages.message(
"argument.mismatch.html.tooltip",
Integer.valueOf(cols - parameters.length + 1), parensizedName,
@@ -833,22 +840,24 @@ public class HighlightMethodUtil {
}
private static String createMismatchedArgumentsHtmlTooltip(PsiExpressionList list,
MethodCandidateInfo info,
PsiParameter[] parameters,
String methodName,
PsiSubstitutor substitutor,
PsiClass aClass) {
return Math.max(parameters.length, list.getExpressions().length) <= 2
? createShortMismatchedArgumentsHtmlTooltip(list, parameters, methodName, substitutor, aClass)
: createLongMismatchedArgumentsHtmlTooltip(list, parameters, methodName, substitutor, aClass);
? createShortMismatchedArgumentsHtmlTooltip(list, info, parameters, methodName, substitutor, aClass)
: createLongMismatchedArgumentsHtmlTooltip(list, info, parameters, methodName, substitutor, aClass);
}
@SuppressWarnings("StringContatenationInLoop")
@Language("HTML")
private static String createLongMismatchedArgumentsHtmlTooltip(PsiExpressionList list,
PsiParameter[] parameters,
String methodName,
PsiSubstitutor substitutor,
PsiClass aClass) {
@Nullable MethodCandidateInfo info,
PsiParameter[] parameters,
String methodName,
PsiSubstitutor substitutor,
PsiClass aClass) {
PsiExpression[] expressions = list.getExpressions();
@SuppressWarnings("NonConstantStringShouldBeStringBuffer") @NonNls
@@ -904,7 +913,7 @@ public class HighlightMethodUtil {
}
s+= "</table>";
final String errorMessage = InferenceSession.getInferenceErrorMessage(list.getParent());
final String errorMessage = info != null ? info.getInferenceErrorMessage() : null;
if (errorMessage != null) {
s+= "reason: ";
s += XmlStringUtil.escapeString(errorMessage).replaceAll("\n", "<br/>");
@@ -1459,7 +1468,7 @@ public class HighlightMethodUtil {
String constructorName = aClass.getName();
String argTypes = buildArgTypesList(list);
String description = JavaErrorMessages.message("wrong.constructor.arguments", constructorName+"()", argTypes);
String tooltip = createMismatchedArgumentsHtmlTooltip(list, PsiParameter.EMPTY_ARRAY, constructorName, PsiSubstitutor.EMPTY, aClass);
String tooltip = createMismatchedArgumentsHtmlTooltip(list, null, PsiParameter.EMPTY_ARRAY, constructorName, PsiSubstitutor.EMPTY, aClass);
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).description(description).escapedToolTip(tooltip).navigationShift(+1).create();
QuickFixAction.registerQuickFixAction(info, constructorCall.getTextRange(), QUICK_FIX_FACTORY.createCreateConstructorFromCallFix(constructorCall));
if (classReference != null) {
@@ -38,9 +38,9 @@ import com.intellij.psi.*;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.impl.source.javadoc.PsiDocMethodOrFieldRef;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.util.*;
@@ -324,7 +324,14 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
else {
if (!LambdaUtil.isLambdaFullyInferred(expression, functionalInterfaceType) && !expression.hasFormalParameterTypes()) {
final PsiCallExpression callExpression = PsiTreeUtil.getParentOfType(expression, PsiCallExpression.class);
String description = callExpression != null ? InferenceSession.getInferenceErrorMessage(callExpression) : null;
String description;
if (callExpression != null) {
final JavaResolveResult result = callExpression.resolveMethodGenerics();
description = result instanceof MethodCandidateInfo ? ((MethodCandidateInfo)result).getInferenceErrorMessage() : null;
}
else {
description = null;
}
if (description == null) {
description = "Cyclic inference";
}
@@ -17,6 +17,7 @@ package com.intellij.psi;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
@@ -193,4 +194,22 @@ public class PsiIntersectionType extends PsiType.Stub {
}
return sb.toString();
}
public String getConflictingConjunctsMessage() {
final PsiType[] conjuncts = getConjuncts();
for (int i = 0; i < conjuncts.length; i++) {
PsiClass conjunct = PsiUtil.resolveClassInClassTypeOnly(conjuncts[i]);
if (conjunct != null && !conjunct.isInterface()) {
for (int i1 = i + 1; i1 < conjuncts.length; i1++) {
PsiClass oppositeConjunct = PsiUtil.resolveClassInClassTypeOnly(conjuncts[i1]);
if (oppositeConjunct != null && !oppositeConjunct.isInterface()) {
if (!conjunct.isInheritor(oppositeConjunct, true) && !oppositeConjunct.isInheritor(conjunct, true)) {
return conjuncts[i].getPresentableText() + " and " + conjuncts[i1].getPresentableText();
}
}
}
}
}
return null;
}
}
@@ -45,6 +45,8 @@ public class MethodCandidateInfo extends CandidateInfo{
private final PsiType[] myArgumentTypes;
private final PsiType[] myTypeArguments;
private PsiSubstitutor myCalcedSubstitutor; // benign race
private String myInferenceError;
private final LanguageLevel myLanguageLevel;
public MethodCandidateInfo(@NotNull PsiElement candidate,
@@ -353,6 +355,14 @@ public class MethodCandidateInfo extends CandidateInfo{
return 31 * super.hashCode() + (isVarargs() ? 1 : 0);
}
public void setInferenceError(String inferenceError) {
myInferenceError = inferenceError;
}
public String getInferenceErrorMessage() {
return myInferenceError;
}
public static class CurrentCandidateProperties {
private final MethodCandidateInfo myMethod;
private PsiSubstitutor mySubstitutor;
@@ -58,7 +58,6 @@ public class InferenceSession {
}
};
private static final Key<List<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";
@@ -71,6 +70,8 @@ public class InferenceSession {
private final PsiManager myManager;
private int myConstraintIdx = 0;
private List<String> myErrorMessages;
private boolean myErased = false;
private final InferenceIncorporationPhase myIncorporationPhase = new InferenceIncorporationPhase(this);
@@ -287,56 +288,63 @@ public class InferenceSession {
@Nullable PsiExpression[] args,
@Nullable PsiElement parent) {
final MethodCandidateInfo.CurrentCandidateProperties properties = getCurrentProperties(parent);
if (!repeatInferencePhases(true)) {
//inferred result would be checked as candidate won't be applicable
return resolveSubset(myInferenceVariables, mySiteSubstitutor);
}
if (properties != null && !properties.isApplicabilityCheck()) {
initReturnTypeConstraint(properties.getMethod(), (PsiCall)parent);
try {
if (!repeatInferencePhases(true)) {
//inferred result would be checked as candidate won't be applicable
return resolveSubset(myInferenceVariables, mySiteSubstitutor);
}
if (properties != null && !properties.isApplicabilityCheck()) {
initReturnTypeConstraint(properties.getMethod(), (PsiCall)parent);
if (!repeatInferencePhases(true)) {
return prepareSubstitution();
}
if (parameters != null && args != null) {
final Set<ConstraintFormula> additionalConstraints = new LinkedHashSet<ConstraintFormula>();
if (parameters.length > 0) {
collectAdditionalConstraints(parameters, args, properties.getMethod(), mySiteSubstitutor, additionalConstraints, properties.isVarargs());
}
if (!additionalConstraints.isEmpty() && !proceedWithAdditionalConstraints(additionalConstraints)) {
return prepareSubstitution().putAll(retrieveNonPrimitiveEqualsBounds(myInferenceVariables));
}
}
}
final PsiSubstitutor substitutor = resolveBounds(myInferenceVariables, PsiSubstitutor.EMPTY);
if (substitutor != null) {
if (myContext != null) {
myContext.putUserData(ERASED, myErased);
}
final Map<PsiTypeParameter, PsiType> map = substitutor.getSubstitutionMap();
for (PsiTypeParameter parameter : map.keySet()) {
final PsiType mapping = map.get(parameter);
PsiTypeParameter param;
if (parameter instanceof InferenceVariable) {
((InferenceVariable)parameter).setInstantiation(mapping);
if (((InferenceVariable)parameter).getCallContext() != myContext) {
//don't include in result substitutor foreign inference variables
continue;
}
param = ((InferenceVariable)parameter).getParameter();
}
else {
param = parameter;
}
mySiteSubstitutor = mySiteSubstitutor.put(param, mapping);
}
} else {
return prepareSubstitution();
}
if (parameters != null && args != null) {
final Set<ConstraintFormula> additionalConstraints = new LinkedHashSet<ConstraintFormula>();
if (parameters.length > 0) {
collectAdditionalConstraints(parameters, args, properties.getMethod(), mySiteSubstitutor, additionalConstraints, properties.isVarargs());
}
if (!additionalConstraints.isEmpty() && !proceedWithAdditionalConstraints(additionalConstraints)) {
return prepareSubstitution().putAll(retrieveNonPrimitiveEqualsBounds(myInferenceVariables));
}
}
}
final PsiSubstitutor substitutor = resolveBounds(myInferenceVariables, PsiSubstitutor.EMPTY);
if (substitutor != null) {
if (myContext != null) {
myContext.putUserData(ERASED, myErased);
}
final Map<PsiTypeParameter, PsiType> map = substitutor.getSubstitutionMap();
for (PsiTypeParameter parameter : map.keySet()) {
final PsiType mapping = map.get(parameter);
PsiTypeParameter param;
if (parameter instanceof InferenceVariable) {
((InferenceVariable)parameter).setInstantiation(mapping);
if (((InferenceVariable)parameter).getCallContext() != myContext) {
//don't include in result substitutor foreign inference variables
continue;
}
param = ((InferenceVariable)parameter).getParameter();
}
else {
param = parameter;
}
mySiteSubstitutor = mySiteSubstitutor.put(param, mapping);
}
} else {
return prepareSubstitution();
}
return prepareSubstitution();
finally {
if (properties != null && myErrorMessages != null) {
properties.getInfo().setInferenceError(StringUtil.join(myErrorMessages, "\n"));
}
}
}
private void collectAdditionalConstraints(PsiParameter[] parameters,
@@ -1029,6 +1037,14 @@ public class InferenceSession {
else {
type = myErased ? null : upperBound;
}
if (type instanceof PsiIntersectionType) {
final String conflictingConjunctsMessage = ((PsiIntersectionType)type).getConflictingConjunctsMessage();
if (conflictingConjunctsMessage != null) {
registerIncompatibleErrorMessage("Type parameter " + var.getName() + " has incompatible upper bounds: " + conflictingConjunctsMessage);
return PsiType.NULL;
}
}
}
else {
for (PsiType upperType : var.getBounds(InferenceBound.UPPER)) {
@@ -1051,25 +1067,12 @@ public class InferenceSession {
}
private void registerIncompatibleErrorMessage(String value) {
if (myContext != null) {
List<String> errorMessage = myContext.getUserData(INFERENCE_FAILURE_MESSAGE);
if (errorMessage == null) {
errorMessage = Collections.synchronizedList(new ArrayList<String>());
myContext.putUserData(INFERENCE_FAILURE_MESSAGE, errorMessage);
}
if (!errorMessage.contains(value)) {
errorMessage.add(value);
}
if (myErrorMessages == null) {
myErrorMessages = new ArrayList<String>();
}
}
@Nullable
public static String getInferenceErrorMessage(@NotNull PsiElement context) {
final List<String> errors = context.getUserData(INFERENCE_FAILURE_MESSAGE);
if (errors != null) {
return StringUtil.join(errors, "\n");
if (!myErrorMessages.contains(value)) {
myErrorMessages.add(value);
}
return null;
}
private String incompatibleBoundsMessage(final InferenceVariable var,
@@ -0,0 +1,14 @@
class Test {
public void testConsume() {
consume(exception());
}
public static void consume(Throwable t) {}
public static void consume(String s) {}
public static <E extends Exception> E exception() {
return null;
}
}
@@ -335,6 +335,10 @@ public class GraphInferenceHighlightingTest extends LightDaemonAnalyzerTestCase
doTest();
}
public void testDoNotIgnoreConflictingUpperBounds() throws Exception {
doTest();
}
private void doTest() throws Exception {
doTest(false);
}