diff --git a/java/java-psi-api/src/com/intellij/psi/PsiDiamondType.java b/java/java-psi-api/src/com/intellij/psi/PsiDiamondType.java index 5da222421d35..e3ca8fec6369 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiDiamondType.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiDiamondType.java @@ -16,12 +16,16 @@ package com.intellij.psi; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.RecursionGuard; +import com.intellij.openapi.util.RecursionManager; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; public abstract class PsiDiamondType extends PsiType { + public static RecursionGuard ourDiamondGuard = RecursionManager.createGuard("diamondInference"); + public PsiDiamondType(PsiAnnotation[] annotations) { super(annotations); } diff --git a/java/java-psi-api/src/com/intellij/psi/PsiResolveHelper.java b/java/java-psi-api/src/com/intellij/psi/PsiResolveHelper.java index 5a035acac1f6..d97216077be9 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiResolveHelper.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiResolveHelper.java @@ -32,6 +32,7 @@ import org.jetbrains.annotations.Nullable; */ public interface PsiResolveHelper { RecursionGuard ourGuard = RecursionManager.createGuard("typeArgInference"); + RecursionGuard ourGraphGuard = RecursionManager.createGuard("graphTypeArgInference"); class SERVICE { private SERVICE() { diff --git a/java/java-psi-api/src/com/intellij/psi/infos/MethodCandidateInfo.java b/java/java-psi-api/src/com/intellij/psi/infos/MethodCandidateInfo.java index 8c6f4c4ed477..c8d193f8e4e1 100644 --- a/java/java-psi-api/src/com/intellij/psi/infos/MethodCandidateInfo.java +++ b/java/java-psi-api/src/com/intellij/psi/infos/MethodCandidateInfo.java @@ -18,6 +18,7 @@ package com.intellij.psi.infos; import com.intellij.openapi.projectRoots.JavaSdkVersion; import com.intellij.openapi.projectRoots.JavaVersionService; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.RecursionGuard; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy; @@ -98,11 +99,16 @@ public class MethodCandidateInfo extends CandidateInfo{ PsiSubstitutor incompleteSubstitutor = super.getSubstitutor(); PsiMethod method = getElement(); if (myTypeArguments == null) { - final Set lists = LambdaUtil.ourParams.get(); - if (lists != null && !lists.isEmpty()) { - return inferTypeArguments(DefaultParameterTypeInferencePolicy.INSTANCE); - } - myCalcedSubstitutor = inferTypeArguments(DefaultParameterTypeInferencePolicy.INSTANCE); + final RecursionGuard.StackStamp stackStamp = PsiDiamondType.ourDiamondGuard.markStack(); + + final PsiSubstitutor inferredSubstitutor = inferTypeArguments(DefaultParameterTypeInferencePolicy.INSTANCE); + + final Set lists = LambdaUtil.ourParams.get(); + if (lists != null && !lists.isEmpty() || !stackStamp.mayCacheNow()) { + return inferredSubstitutor; + } + + myCalcedSubstitutor = inferredSubstitutor; } else { PsiTypeParameter[] typeParams = method.getTypeParameters(); diff --git a/java/java-psi-impl/src/com/intellij/psi/PsiDiamondTypeImpl.java b/java/java-psi-impl/src/com/intellij/psi/PsiDiamondTypeImpl.java index 28d43fc1701b..dec7b71f5438 100644 --- a/java/java-psi-impl/src/com/intellij/psi/PsiDiamondTypeImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/PsiDiamondTypeImpl.java @@ -18,6 +18,8 @@ package com.intellij.psi; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy; @@ -121,19 +123,30 @@ public class PsiDiamondTypeImpl extends PsiDiamondType { return resolveInferredTypesNoCheck(newExpression, context); } - public static DiamondInferenceResult resolveInferredTypesNoCheck(PsiNewExpression newExpression, PsiElement context) { + public static DiamondInferenceResult resolveInferredTypesNoCheck(final PsiNewExpression newExpression, final PsiElement context) { final PsiClass psiClass = findClass(newExpression); if (psiClass == null) return DiamondInferenceResult.NULL_RESULT; final PsiExpressionList argumentList = newExpression.getArgumentList(); if (argumentList == null) return DiamondInferenceResult.NULL_RESULT; - final PsiMethod constructor = findConstructor(psiClass, newExpression); - PsiTypeParameter[] params = getAllTypeParams(constructor, psiClass); - PsiMethod staticFactory = generateStaticFactory(constructor, psiClass, params); - if (staticFactory == null) { + final Ref staticFactory = new Ref(); + final PsiSubstitutor inferredSubstitutor = ourDiamondGuard.doPreventingRecursion(newExpression, true, new Computable() { + @Override + public PsiSubstitutor compute() { + final PsiMethod constructor = findConstructor(psiClass, newExpression); + PsiTypeParameter[] params = getAllTypeParams(constructor, psiClass); + + staticFactory.set(generateStaticFactory(constructor, psiClass, params)); + if (staticFactory.get() == null) { + return null; + } + + return inferTypeParametersForStaticFactory(staticFactory.get(), newExpression, context); + } + }); + if (inferredSubstitutor == null) { return DiamondInferenceResult.NULL_RESULT; } - final PsiSubstitutor inferredSubstitutor = inferTypeParametersForStaticFactory(staticFactory, newExpression, context); - final PsiTypeParameter[] parameters = staticFactory.getTypeParameters(); + final PsiTypeParameter[] parameters = staticFactory.get().getTypeParameters(); final PsiTypeParameter[] classParameters = psiClass.getTypeParameters(); final PsiJavaCodeReferenceElement classOrAnonymousClassReference = newExpression.getClassOrAnonymousClassReference(); LOG.assertTrue(classOrAnonymousClassReference != null); diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/JavaResolveCache.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/JavaResolveCache.java index ab6dd7c1b6ee..ef82839992e8 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/JavaResolveCache.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/JavaResolveCache.java @@ -24,6 +24,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.NotNullLazyKey; +import com.intellij.openapi.util.RecursionGuard; import com.intellij.psi.*; import com.intellij.psi.impl.AnyPsiChangeListener; import com.intellij.psi.impl.PsiManagerImpl; @@ -87,7 +88,11 @@ public class JavaResolveCache { public PsiType getType(@NotNull T expr, @NotNull Function f) { PsiType type = getCachedType(expr); if (type == null) { + final RecursionGuard.StackStamp stackStamp = PsiDiamondType.ourDiamondGuard.markStack(); type = f.fun(expr); + if (!stackStamp.mayCacheNow()) { + return type; + } if (type == null) type = TypeConversionUtil.NULL_TYPE; Reference ref = new SoftReference(type); Reference storedRef = ConcurrencyUtil.cacheOrGet(myCalculatedTypes, expr, ref); diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/PsiResolveHelperImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/PsiResolveHelperImpl.java index ec61bb7cc08e..13ac4e798d0f 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/PsiResolveHelperImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/PsiResolveHelperImpl.java @@ -47,13 +47,6 @@ public class PsiResolveHelperImpl implements PsiResolveHelper { public static final Pair RAW_INFERENCE = new Pair(null, ConstraintType.EQUALS); private final PsiManager myManager; - public static final ThreadLocal> OUR_CONSTRUCTORS = new ThreadLocal>(){ - @Override - protected Map initialValue() { - return new HashMap(); - } - }; - public PsiResolveHelperImpl(PsiManager manager) { myManager = manager; } @@ -83,13 +76,7 @@ public class PsiResolveHelperImpl implements PsiResolveHelper { substitutor = substitutor.putAll(TypeConversionUtil.getSuperClassSubstitutor(aClass, anonymous, substitutor)); } else { - final PsiType alreadyThere = OUR_CONSTRUCTORS.get().put(argumentList, type); - try { - processor = new MethodResolverProcessor(aClass, argumentList, place); - } - finally { - if (alreadyThere == null) OUR_CONSTRUCTORS.get().remove(argumentList); - } + processor = new MethodResolverProcessor(aClass, argumentList, place); } ResolveState state = ResolveState.initial().put(PsiSubstitutor.KEY, substitutor); @@ -175,11 +162,16 @@ public class PsiResolveHelperImpl implements PsiResolveHelper { PsiType[] argTypes = new PsiType[arguments.length]; if (parameters.length > 0) { for (int j = 0; j < argTypes.length; j++) { - PsiExpression argument = arguments[j]; + final PsiExpression argument = arguments[j]; if (argument == null) continue; if (argument instanceof PsiMethodCallExpression && ourGuard.currentStack().contains(argument)) continue; - if (ourGraphGuard.currentStack().contains(argument)) continue; + + final RecursionGuard.StackStamp stackStamp = PsiDiamondType.ourDiamondGuard.markStack(); argTypes[j] = argument.getType(); + if (!stackStamp.mayCacheNow()) { + argTypes[j] = null; + continue; + } final PsiParameter parameter = parameters[Math.min(j, parameters.length - 1)]; if (j >= parameters.length && !parameter.isVarArgs()) break; @@ -1164,7 +1156,6 @@ public class PsiResolveHelperImpl implements PsiResolveHelper { return result; } - private static RecursionGuard ourGraphGuard = RecursionManager.createGuard("graphTypeArgInference"); private static final ProcessCandidateParameterTypeInferencePolicy GRAPH_INFERENCE_POLICY = new GraphInferencePolicy(); private static Pair graphInferenceFromCallContext(@NotNull final PsiExpression methodCall, @@ -1172,8 +1163,8 @@ public class PsiResolveHelperImpl implements PsiResolveHelper { @NotNull final PsiCallExpression parentCall) { if (Registry.is("disable.graph.inference", false)) return null; final PsiExpressionList argumentList = parentCall.getArgumentList(); - final PsiType inferDiamond = OUR_CONSTRUCTORS.get().get(argumentList); - if (inferDiamond != null) { + if (PsiDiamondType.ourDiamondGuard.currentStack().contains(parentCall)) { + PsiDiamondType.ourDiamondGuard.prohibitResultCaching(parentCall); return FAILED_INFERENCE; } return ourGraphGuard.doPreventingRecursion(methodCall, true, new Computable>() { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/graphInference/DiamondsUsedToDetectArgumentType.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/graphInference/DiamondsUsedToDetectArgumentType.java new file mode 100644 index 000000000000..4b8e10ddefc2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/graphInference/DiamondsUsedToDetectArgumentType.java @@ -0,0 +1,105 @@ +class FindOp { + + public static FindOp> makeRef() { + return new FindOp<>(Optional.empty(), Optional::isPresent, FindSink.OfRef::new); + } + + + static abstract class FindSink implements TerminalSink { + @Override + public void accept(FindSinkT value) {} + static class OfRef extends FindSink> { + @Override + public Optional getAndClearState() { + return null; + } + } + } + + private FindOp(O emptyValue, + Predicate presentPredicate, + Supplier> sinkSupplier) {} +} + +class FindOp1 { + + public static FindOp1> makeRef() { + return new FindOp1<>(Optional.empty(), Optional::isPresent); + } + + public static FindOp1> makeRef1() { + return new FindOp1<>(Optional.empty(), t -> t.isPresent()); + } + + + private FindOp1(O emptyValue, Predicate presentPredicate) {} +} + +class FindOp2 { + + public static FindOp2> makeRef() { + return new FindOp2<>(Optional.empty()); + } + + private FindOp2(O emptyValue) {} +} + +final class Optional { + private Optional(T value) { + } + + private Optional() { + } + @SuppressWarnings("unchecked") + public static Optional empty() { + return null; + } + + public static Optional of(T value) { + return new Optional<>(value); + } + + public T get() { + return null; + } + + public boolean isPresent() { + return true; + } +} + +interface Predicate { + public boolean test(T t); +} +interface Supplier { + public T get(); +} +interface Consumer { + public void accept(T t); +} + +interface IntConsumer { + public void accept(int value); +} + +interface Sink extends Consumer { + + + default void accept(int value) { + throw new IllegalStateException("called wrong accept method"); + } + @FunctionalInterface + interface OfInt extends Sink, IntConsumer { + @Override + void accept(int value); + + @Override + default void accept(Integer i) { + accept(i.intValue()); + } + } +} + +interface TerminalSink extends Sink { + R getAndClearState(); +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java index cdf526e7a133..d297ab237ba0 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java @@ -68,6 +68,10 @@ public class GraphInferenceHighlightingTest extends LightDaemonAnalyzerTestCase doTest(); } + public void testDiamondsUsedToDetectArgumentType() throws Exception { + doTest(); + } + private void doTest() throws Exception { doTest(false); }