inference: restore captured wildcard from fresh type parameter (IDEA-165871; IDEA-176592)

register return type constrain replaces wildcard parametrization with fresh variables aka custom capture conversion. When the specification would deal with these fresh type parameters further, IDEA expects them like PsiCapturedWildcards. Let's restore captured wildcards when inference failed to add any new constraints for fresh variables but initial upper bound of captured wildcard
This commit is contained in:
Anna.Kozlova
2017-08-18 19:07:17 +02:00
parent 4875358e89
commit 07fa74c8b5
8 changed files with 95 additions and 24 deletions
@@ -149,7 +149,7 @@ public class InferenceIncorporationPhase {
if (aType instanceof PsiWildcardType) {
for (PsiType eqBound : eqBounds) {
if (!isInferenceVariableOrFreshTypeParameter(eqBound)) {
if (!isInferenceVariableOrFreshTypeParameter(inferenceVariable, eqBound)) {
return false;
}
}
@@ -177,7 +177,7 @@ public class InferenceIncorporationPhase {
}
for (PsiType lowerBound : lowerBounds) {
if (isInferenceVariableOrFreshTypeParameter(lowerBound)) {
if (isInferenceVariableOrFreshTypeParameter(inferenceVariable, lowerBound)) {
return false;
}
}
@@ -198,7 +198,7 @@ public class InferenceIncorporationPhase {
}
for (PsiType lowerBound : lowerBounds) {
if (isInferenceVariableOrFreshTypeParameter(lowerBound)) {
if (isInferenceVariableOrFreshTypeParameter(inferenceVariable, lowerBound)) {
return false;
}
}
@@ -239,10 +239,12 @@ public class InferenceIncorporationPhase {
}
}
private static Boolean isInferenceVariableOrFreshTypeParameter(PsiType eqBound) {
private static Boolean isInferenceVariableOrFreshTypeParameter(InferenceVariable inferenceVariable,
PsiType eqBound) {
final PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(eqBound);
if (psiClass instanceof InferenceVariable ||
psiClass instanceof PsiTypeParameter && TypeConversionUtil.isFreshVariable((PsiTypeParameter)psiClass)) return true;
psiClass instanceof PsiTypeParameter && TypeConversionUtil.isFreshVariable((PsiTypeParameter)psiClass) ||
eqBound instanceof PsiCapturedWildcardType && eqBound.equals(inferenceVariable.getUserData(InferenceSession.ORIGINAL_CAPTURE))) return true;
return false;
}
@@ -69,6 +69,7 @@ public class InferenceSession {
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";
static final Key<PsiCapturedWildcardType> ORIGINAL_CAPTURE = Key.create("ORIGINAL_CAPTURE");
private final Set<InferenceVariable> myInferenceVariables = new LinkedHashSet<>();
private final List<ConstraintFormula> myConstraints = new ArrayList<>();
@@ -389,7 +390,7 @@ public class InferenceSession {
if (targetType != null && !PsiType.VOID.equals(targetType)) {
PsiType actualType = PsiUtil.isRawSubstitutor(method, mySiteSubstitutor) ? returnType : mySiteSubstitutor.substitute(returnType);
registerReturnTypeConstraints(actualType, targetType);
registerReturnTypeConstraints(actualType, targetType, myContext);
expectedActualErrorMessage = "Incompatible types. Required " + targetType.getPresentableText() + " but '" + method.getName() + "' was inferred to " + actualType.getPresentableText() + ":";
}
}
@@ -704,7 +705,7 @@ public class InferenceSession {
return result.toArray(new InferenceVariable[result.size()]);
}
public void registerReturnTypeConstraints(PsiType returnType, PsiType targetType) {
public void registerReturnTypeConstraints(PsiType returnType, PsiType targetType, PsiElement context) {
returnType = substituteWithInferenceVariables(returnType);
if (myErased) {
final InferenceVariable inferenceVariable = getInferenceVariable(returnType);
@@ -720,7 +721,7 @@ public class InferenceSession {
final PsiClass psiClass = resolveResult.getElement();
if (psiClass != null) {
LOG.assertTrue(returnType instanceof PsiClassType);
PsiClassType substitutedCapture = (PsiClassType)PsiUtil.captureToplevelWildcards(returnType, myContext);
PsiClassType substitutedCapture = (PsiClassType)PsiUtil.captureToplevelWildcards(returnType, context);
final PsiTypeParameter[] typeParameters = psiClass.getTypeParameters();
final PsiType[] parameters = substitutedCapture.getParameters();
final InferenceVariable[] copy = initFreshVariablesForCapturedBounds(typeParameters, parameters);
@@ -744,7 +745,7 @@ public class InferenceSession {
final PsiSubstitutor substitutor = resolveSubset(Collections.singletonList(inferenceVariable), mySiteSubstitutor);
final PsiType substitutedReturnType = substitutor.substitute(inferenceVariable);
if (substitutedReturnType != null) {
addConstraint(new TypeCompatibilityConstraint(targetType, PsiUtil.captureToplevelWildcards(substitutedReturnType, myContext)));
addConstraint(new TypeCompatibilityConstraint(targetType, PsiUtil.captureToplevelWildcards(substitutedReturnType, context)));
}
}
else {
@@ -767,7 +768,14 @@ public class InferenceSession {
restParamSubstitution = restParamSubstitution.put(typeParameters[i], parameter);
}
}
return initBounds(null, restParamSubstitution, capturedParams.toArray(PsiTypeParameter.EMPTY_ARRAY));
InferenceVariable[] variables = initBounds(null, restParamSubstitution, capturedParams.toArray(PsiTypeParameter.EMPTY_ARRAY));
int idx = 0;
for (PsiType parameter : parameters) {
if (parameter instanceof PsiCapturedWildcardType) {
variables[idx++].putUserData(ORIGINAL_CAPTURE, (PsiCapturedWildcardType)parameter);
}
}
return variables;
}
return initBounds(null, typeParameters);
}
@@ -1219,12 +1227,22 @@ public class InferenceSession {
}
private PsiType checkBoundsConsistency(PsiSubstitutor substitutor, InferenceVariable var) {
final PsiType eqBound = getEqualsBound(var, substitutor);
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)) {
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(eqBound);
if (aClass instanceof PsiTypeParameter && TypeConversionUtil.isFreshVariable((PsiTypeParameter)aClass)) {
PsiCapturedWildcardType capturedWildcard = var.getUserData(ORIGINAL_CAPTURE);
//restore captured wildcard from method return type when no additional constraints were inferred
//to preserve equality of type arguments
if (capturedWildcard != null && capturedWildcard.getUpperBound().equals(getUpperBound(aClass))) {
eqBound = capturedWildcard;
}
}
if (lowerBound != PsiType.NULL && !TypeConversionUtil.isAssignable(eqBound, lowerBound)) {
final String incompatibleBoundsMessage =
incompatibleBoundsMessage(var, substitutor, InferenceBound.EQ, EQUALITY_CONSTRAINTS_PRESENTATION, InferenceBound.LOWER, LOWER_BOUNDS_PRESENTATION);
@@ -277,6 +277,7 @@ public class InferenceSessionContainer {
if (variable.isThrownBound()) {
newVariable.setThrownBound();
}
newVariable.putUserData(InferenceSession.ORIGINAL_CAPTURE, variable.getUserData(InferenceSession.ORIGINAL_CAPTURE));
}
for (int i = 0; i < targetVars.size(); i++) {
@@ -16,6 +16,7 @@
package com.intellij.psi.impl.source.resolve.graphInference.constraints;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceVariable;
@@ -54,11 +55,11 @@ public class ExpressionCompatibilityConstraint extends InputOutputConstraintForm
}
return assignmentCompatible;
}
if (exprType instanceof PsiLambdaParameterType) {
return false;
}
if (exprType instanceof PsiClassType) {
if (((PsiClassType)exprType).resolve() == null) {
return true;
@@ -81,7 +82,7 @@ public class ExpressionCompatibilityConstraint extends InputOutputConstraintForm
return true;
}
}
if (myExpression instanceof PsiConditionalExpression) {
final PsiExpression thenExpression = ((PsiConditionalExpression)myExpression).getThenExpression();
if (thenExpression != null) {
@@ -94,7 +95,7 @@ public class ExpressionCompatibilityConstraint extends InputOutputConstraintForm
}
return true;
}
if (myExpression instanceof PsiCall) {
final InferenceSession callSession = reduceExpressionCompatibilityConstraint(session, myExpression, myT, true);
if (callSession == null) {
@@ -103,22 +104,25 @@ public class ExpressionCompatibilityConstraint extends InputOutputConstraintForm
if (callSession != session) {
session.getInferenceSessionContainer().registerNestedSession(callSession);
session.propagateVariables(callSession.getInferenceVariables(), callSession.getRestoreNameSubstitution());
for (Pair<InferenceVariable[], PsiClassType> pair : callSession.myIncorporationPhase.getCaptures()) {
session.myIncorporationPhase.addCapture(pair.first, pair.second);
}
callSession.setUncheckedInContext();
}
return true;
}
if (myExpression instanceof PsiMethodReferenceExpression) {
constraints.add(new PsiMethodReferenceCompatibilityConstraint(((PsiMethodReferenceExpression)myExpression), myT));
return true;
}
if (myExpression instanceof PsiLambdaExpression) {
constraints.add(new LambdaExpressionCompatibilityConstraint((PsiLambdaExpression)myExpression, myT));
return true;
}
return true;
}
@@ -173,7 +177,7 @@ public class ExpressionCompatibilityConstraint extends InputOutputConstraintForm
}
if (returnType != null) {
callSession.registerReturnTypeConstraints(siteSubstitutor.substitute(returnType), targetType);
callSession.registerReturnTypeConstraints(siteSubstitutor.substitute(returnType), targetType, expression);
}
if (callSession.repeatInferencePhases()) {
return callSession;
@@ -189,7 +189,7 @@ public class PsiMethodReferenceCompatibilityConstraint implements ConstraintForm
//the constraint reduces to the bound set B3 which would be used to determine the method reference's invocation type
//when targeting the return type of the function type, as defined in 18.5.2.
session.collectApplicabilityConstraints(myExpression, ((MethodCandidateInfo)resolve), groundTargetType);
session.registerReturnTypeConstraints(psiSubstitutor.substitute(referencedMethodReturnType), returnType);
session.registerReturnTypeConstraints(psiSubstitutor.substitute(referencedMethodReturnType), returnType, myExpression);
return true;
}
}
@@ -69,7 +69,7 @@ public class MethodReferenceResolver implements ResolveCache.PolyVariantContextR
final PsiClassType returnType = composeReturnType(containingClass, substitutor);
final InferenceSession session = new InferenceSession(containingClass.getTypeParameters(), substitutor, reference.getManager(), null);
if (!(session.isProperType(session.substituteWithInferenceVariables(returnType)) && session.isProperType(interfaceMethodReturnType))) {
session.registerReturnTypeConstraints(returnType, interfaceMethodReturnType);
session.registerReturnTypeConstraints(returnType, interfaceMethodReturnType, reference);
substitutor = session.infer();
}
}
@@ -95,7 +95,7 @@ public class MethodReferenceResolver implements ResolveCache.PolyVariantContextR
protected MethodCandidateInfo createCandidateInfo(@NotNull final PsiMethod method,
@NotNull final PsiSubstitutor substitutor,
final boolean staticProblem,
final boolean accessible,
final boolean accessible,
final boolean varargs) {
final PsiExpressionList argumentList = getArgumentList();
final PsiType[] typeParameters = reference.getTypeParameters();
@@ -131,7 +131,7 @@ public class MethodReferenceResolver implements ResolveCache.PolyVariantContextR
PsiSubstitutor subst = PsiMethodReferenceCompatibilityConstraint.getSubstitutor(signature, qualifierResolveResult, method, containingClass, reference);
final PsiType returnType = method.isConstructor() ? composeReturnType(containingClass, subst) : subst.substitute(method.getReturnType());
if (returnType != null) {
session.registerReturnTypeConstraints(returnType, interfaceMethodReturnType);
session.registerReturnTypeConstraints(returnType, interfaceMethodReturnType, reference);
}
}
return session.infer(method.getParameterList().getParameters(), null, null);
@@ -0,0 +1,45 @@
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.*;
abstract class MapToGeneric {
void sdf(Stream<MapToGeneric> stream) {
stream.map(mapToGeneric -> mapToGeneric.map("123"));
}
abstract <T> Map<?, T> map(T t);
}
abstract class MapToGenericSimplified {
{
bar(() -> map()) ;
bar(this::map) ;
}
abstract <T> Map<T, ?> map();
abstract <R> R bar(Supplier<R> mapper);
}
class Test {
static class MyList<T, X> extends ArrayList<X> {}
static <T> MyList<? extends Number, T> create() {
return new MyList<>();
}
MyList<? extends Number, ? extends Number> test(List<? extends Number> list) {
return list.stream().collect(Collectors.toCollection(Test::create));
}
MyList<? extends Number, ? extends Number> testLambda(List<? extends Number> list) {
return list.stream().collect(Collectors.toCollection(() -> create()));
}
}
@@ -179,6 +179,7 @@ public class GraphInferenceHighlightingTest extends LightDaemonAnalyzerTestCase
public void testFreshVariablesDuringApplicabilityCheck() { doTest(); }
public void testPertinentToApplicabilityCheckForBlockLambda() { doTest(); }
public void testRestoreCapturedWildcardsInReturnTypesWhenNoAdditionalConstraintsDetected() { doTest(); }
public void testApplicabilityCheckFailsExpressionTypeCheckPasses() {
doTest();