From 07fa74c8b5c230997d6184720e661eea461553db Mon Sep 17 00:00:00 2001 From: "Anna.Kozlova" Date: Fri, 18 Aug 2017 19:01:30 +0200 Subject: [PATCH] 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 --- .../InferenceIncorporationPhase.java | 12 ++--- .../graphInference/InferenceSession.java | 30 ++++++++++--- .../InferenceSessionContainer.java | 1 + .../ExpressionCompatibilityConstraint.java | 22 +++++---- ...ethodReferenceCompatibilityConstraint.java | 2 +- .../tree/java/MethodReferenceResolver.java | 6 +-- ...esWhenNoAdditionalConstraintsDetected.java | 45 +++++++++++++++++++ .../GraphInferenceHighlightingTest.java | 1 + 8 files changed, 95 insertions(+), 24 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/graphInference/RestoreCapturedWildcardsInReturnTypesWhenNoAdditionalConstraintsDetected.java diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceIncorporationPhase.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceIncorporationPhase.java index 340e56425325..c2758a2cfe8c 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceIncorporationPhase.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceIncorporationPhase.java @@ -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; } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java index e8e0a5f66c8e..0aa7afd83c93 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java @@ -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 ORIGINAL_CAPTURE = Key.create("ORIGINAL_CAPTURE"); private final Set myInferenceVariables = new LinkedHashSet<>(); private final List 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); diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSessionContainer.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSessionContainer.java index 4ba0d7e723f1..daa9d59acdbe 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSessionContainer.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSessionContainer.java @@ -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++) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/ExpressionCompatibilityConstraint.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/ExpressionCompatibilityConstraint.java index 435749e24ec9..8b4ba657e6d5 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/ExpressionCompatibilityConstraint.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/ExpressionCompatibilityConstraint.java @@ -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 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; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/PsiMethodReferenceCompatibilityConstraint.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/PsiMethodReferenceCompatibilityConstraint.java index 78897aac45cc..fccdd0395f1c 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/PsiMethodReferenceCompatibilityConstraint.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/constraints/PsiMethodReferenceCompatibilityConstraint.java @@ -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; } } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/MethodReferenceResolver.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/MethodReferenceResolver.java index d0d2081c92a0..74f6eb778fd6 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/MethodReferenceResolver.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/MethodReferenceResolver.java @@ -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); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/graphInference/RestoreCapturedWildcardsInReturnTypesWhenNoAdditionalConstraintsDetected.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/graphInference/RestoreCapturedWildcardsInReturnTypesWhenNoAdditionalConstraintsDetected.java new file mode 100644 index 000000000000..3643bbc4ba1f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/graphInference/RestoreCapturedWildcardsInReturnTypesWhenNoAdditionalConstraintsDetected.java @@ -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 stream) { + stream.map(mapToGeneric -> mapToGeneric.map("123")); + } + + abstract Map map(T t); +} + + +abstract class MapToGenericSimplified { + { + bar(() -> map()) ; + bar(this::map) ; + } + + abstract Map map(); + + abstract R bar(Supplier mapper); +} + +class Test { + static class MyList extends ArrayList {} + + static MyList create() { + return new MyList<>(); + } + + MyList test(List list) { + + return list.stream().collect(Collectors.toCollection(Test::create)); + } + + MyList testLambda(List list) { + + return list.stream().collect(Collectors.toCollection(() -> create())); + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java index 9d534da56032..2aad6e703571 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java @@ -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();