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();