java: prohibit caching when using thread-local types imposed on expressions and declarations in complex nested inference scenarios

GitOrigin-RevId: a767826dedb5d1879009ee5804e1c675d1b863ac
This commit is contained in:
peter
2019-05-27 09:04:05 +03:00
committed by intellij-monorepo-bot
parent a086de2ef0
commit 000ccd1dbc
13 changed files with 120 additions and 115 deletions
@@ -23,7 +23,6 @@ import java.util.function.Supplier;
* @author anna
*/
public class LambdaUtil {
private static final ThreadLocal<Map<PsiElement, PsiType>> ourFunctionTypes = new ThreadLocal<>();
private static final Logger LOG = Logger.getInstance(LambdaUtil.class);
@Nullable
@@ -355,12 +354,10 @@ public class LambdaUtil {
parent = parent.getParent();
}
final Map<PsiElement, PsiType> map = ourFunctionTypes.get();
if (map != null) {
final PsiType type = ObjectUtils.chooseNotNull(map.get(expression), map.get(element));
if (type != null) {
return type;
}
PsiType type = ThreadLocalTypes.getElementType(expression);
if (type == null) type = ThreadLocalTypes.getElementType(element);
if (type != null) {
return type;
}
if (parent instanceof PsiArrayInitializerExpression) {
@@ -648,16 +645,6 @@ public class LambdaUtil {
return false;
}
@NotNull
public static Map<PsiElement, PsiType> getFunctionalTypeMap() {
Map<PsiElement, PsiType> map = ourFunctionTypes.get();
if (map == null) {
map = new HashMap<>();
ourFunctionTypes.set(map);
}
return map;
}
public static Map<PsiElement, String> checkReturnTypeCompatible(PsiLambdaExpression lambdaExpression, PsiType functionalInterfaceReturnType) {
Map<PsiElement, String> errors = new LinkedHashMap<>();
if (PsiType.VOID.equals(functionalInterfaceReturnType)) {
@@ -751,7 +738,7 @@ public class LambdaUtil {
break;
}
if (getFunctionalTypeMap().containsKey(lambdaExpression)) {
if (ThreadLocalTypes.hasBindingFor(lambdaExpression)) {
break;
}
}
@@ -761,7 +748,7 @@ public class LambdaUtil {
break;
}
if (parent instanceof PsiLambdaExpression && getFunctionalTypeMap().containsKey(parent)) {
if (parent instanceof PsiLambdaExpression && ThreadLocalTypes.hasBindingFor(parent)) {
break;
}
@@ -770,7 +757,7 @@ public class LambdaUtil {
break;
}
if (MethodCandidateInfo.isOverloadCheck(psiCall.getArgumentList()) ||
lambdaExpression != null && getFunctionalTypeMap().containsKey(lambdaExpression)) {
lambdaExpression != null && ThreadLocalTypes.hasBindingFor(lambdaExpression)) {
break;
}
@@ -834,7 +821,7 @@ public class LambdaUtil {
public static <T> T performWithSubstitutedParameterBounds(final PsiTypeParameter[] typeParameters,
final PsiSubstitutor substitutor,
final Supplier<? extends T> producer) {
try {
return ThreadLocalTypes.performWithTypes(map -> {
for (PsiTypeParameter parameter : typeParameters) {
final PsiClassType[] types = parameter.getExtendsListTypes();
if (types.length > 0) {
@@ -842,31 +829,18 @@ public class LambdaUtil {
//don't glb to avoid flattening = Object&Interface would be preserved
//otherwise methods with different signatures could get same erasure
final PsiType upperBound = PsiIntersectionType.createIntersection(false, conjuncts.toArray(PsiType.EMPTY_ARRAY));
getFunctionalTypeMap().put(parameter, upperBound);
map.forceType(parameter, upperBound);
}
}
return producer.get();
}
finally {
for (PsiTypeParameter parameter : typeParameters) {
getFunctionalTypeMap().remove(parameter);
}
}
});
}
public static <T> T performWithTargetType(@NotNull PsiElement element, @NotNull PsiType targetType, @NotNull Supplier<? extends T> producer) {
Map<PsiElement, PsiType> map = getFunctionalTypeMap();
PsiType prev = map.put(element, targetType);
try {
return ThreadLocalTypes.performWithTypes(types -> {
types.forceType(element, targetType);
return producer.get();
}
finally {
if (prev == null) {
map.remove(element);
} else {
map.put(element, prev);
}
}
});
}
/**
@@ -0,0 +1,55 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi;
import com.intellij.openapi.util.RecursionGuard;
import com.intellij.openapi.util.RecursionManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class ThreadLocalTypes {
private static final RecursionGuard<ThreadLocalTypes> ourGuard = RecursionManager.createGuard("ThreadLocalTypes");
private final Map<PsiElement, PsiType> myMap = new HashMap<>();
private ThreadLocalTypes() {}
@Nullable
public static PsiType getElementType(@NotNull PsiElement psi) {
List<? extends ThreadLocalTypes> stack = ourGuard.currentStack();
for (int i = stack.size() - 1; i >= 0; i--) {
ThreadLocalTypes types = stack.get(i);
PsiType type = types.myMap.get(psi);
if (type != null) {
ourGuard.prohibitResultCaching(types);
return type;
}
}
return null;
}
public static boolean hasBindingFor(@NotNull PsiElement psi) {
List<? extends ThreadLocalTypes> stack = ourGuard.currentStack();
for (int i = stack.size() - 1; i >= 0; i--) {
ThreadLocalTypes types = stack.get(i);
if (types.myMap.containsKey(psi)) {
ourGuard.prohibitResultCaching(types);
return true;
}
}
return false;
}
public static <T> T performWithTypes(@NotNull Function<ThreadLocalTypes, T> action) {
ThreadLocalTypes types = new ThreadLocalTypes();
return ourGuard.doPreventingRecursion(types, false, () -> action.apply(types));
}
public void forceType(@NotNull PsiElement psi, @Nullable PsiType type) {
myMap.put(psi, type);
}
}
@@ -35,7 +35,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@@ -183,21 +182,15 @@ public class MethodCandidateInfo extends CandidateInfo{
.map(expression -> PsiUtil.skipParenthesizedExprDown(expression))
.filter(expression -> expression != null && !(expression instanceof PsiFunctionalExpression))
.toArray(PsiExpression[]::new);
Map<PsiElement, PsiType> expressionTypes = LambdaUtil.getFunctionalTypeMap();
try {
return ThreadLocalTypes.performWithTypes(expressionTypes -> {
PsiMethod method = getElement();
boolean varargs = isVarargs();
for (PsiExpression context : expressions) {
expressionTypes.put(context,
PsiTypesUtil.getTypeByMethod(context, argumentList, method, varargs, substitutor, false));
expressionTypes.forceType(context,
PsiTypesUtil.getTypeByMethod(context, argumentList, method, varargs, substitutor, false));
}
return computable.compute();
}
finally {
for (PsiExpression context : expressions) {
expressionTypes.remove(context);
}
}
});
}
else {
return computable.compute();
@@ -987,7 +987,7 @@ public class PsiClassImplUtil {
}
PsiType upperBound = psiClass instanceof PsiTypeParameter ? TypeConversionUtil.getInferredUpperBoundForSynthetic((PsiTypeParameter)psiClass) : null;
if (upperBound == null && psiClass instanceof PsiTypeParameter) {
upperBound = LambdaUtil.getFunctionalTypeMap().get(psiClass);
upperBound = ThreadLocalTypes.getElementType(psiClass);
}
if (upperBound instanceof PsiIntersectionType) {
final PsiType[] conjuncts = ((PsiIntersectionType)upperBound).getConjuncts();
@@ -1030,7 +1030,7 @@ public class PsiClassImplUtil {
}
PsiType upperBound = psiClass instanceof PsiTypeParameter ? TypeConversionUtil.getInferredUpperBoundForSynthetic((PsiTypeParameter)psiClass) : null;
if (upperBound == null && psiClass instanceof PsiTypeParameter) {
upperBound = LambdaUtil.getFunctionalTypeMap().get(psiClass);
upperBound = ThreadLocalTypes.getElementType(psiClass);
}
if (upperBound instanceof PsiIntersectionType) {
final PsiType[] conjuncts = ((PsiIntersectionType)upperBound).getConjuncts();
@@ -778,7 +778,7 @@ public class PsiImplUtil {
}
if (element instanceof PsiMethodReferenceExpression) {
// method refs: do not cache results during parent conflict resolving, acceptable checks, etc
if (LambdaUtil.getFunctionalTypeMap().containsKey(element)) {
if (ThreadLocalTypes.hasBindingFor(element)) {
return (JavaResolveResult[])resolver.resolve(element, psiFile, incompleteCode);
}
}
@@ -54,6 +54,7 @@ public class InferenceSession {
private PsiSubstitutor myInferenceSubstitution = PsiSubstitutor.EMPTY;
private PsiSubstitutor myRestoreNameSubstitution = PsiSubstitutor.EMPTY;
private MethodCandidateInfo myCurrentMethod;
private ThreadLocalTypes myTempTypes;
public InferenceSession(InitialInferenceState initialState, ParameterTypeInferencePolicy policy) {
myContext = initialState.getContext();
@@ -303,24 +304,22 @@ public class InferenceSession {
@Nullable PsiElement parent,
@Nullable MethodCandidateInfo currentMethod,
@NotNull PsiSubstitutor initialSubstitutor) {
try {
doInfer(parameters, args, parent, currentMethod, initialSubstitutor);
return prepareSubstitution();
}
finally {
for (ConstraintFormula formula : myConstraintsCopy) {
if (formula instanceof InputOutputConstraintFormula) {
LambdaUtil.getFunctionalTypeMap().remove(((InputOutputConstraintFormula)formula).getExpression());
}
return ThreadLocalTypes.performWithTypes(types -> {
myTempTypes = types;
try {
doInfer(parameters, args, parent, currentMethod, initialSubstitutor);
return prepareSubstitution();
}
if (currentMethod != null) {
if (myErrorMessages != null) {
currentMethod.setApplicabilityError(StringUtil.join(myErrorMessages, "\n"));
finally {
if (currentMethod != null) {
if (myErrorMessages != null) {
currentMethod.setApplicabilityError(StringUtil.join(myErrorMessages, "\n"));
}
currentMethod.setErased(myErased);
}
currentMethod.setErased(myErased);
myTempTypes = null;
}
}
});
}
private void doInfer(@Nullable PsiParameter[] parameters,
@@ -376,7 +375,7 @@ public class InferenceSession {
}
private static boolean isPertinentToApplicabilityCheckOnContainingCall(@NotNull PsiElement parent) {
return LambdaUtil.getFunctionalTypeMap().containsKey(parent);
return ThreadLocalTypes.hasBindingFor(parent);
}
private void collectAdditionalConstraints(PsiParameter[] parameters,
@@ -791,7 +790,7 @@ public class InferenceSession {
final PsiExpressionList argumentList = ((PsiCall)gParent).getArgumentList();
if (argumentList != null) {
if (MethodCandidateInfo.isOverloadCheck(argumentList)) {
return LambdaUtil.getFunctionalTypeMap().get(context);
return ThreadLocalTypes.getElementType(context);
}
final JavaResolveResult result = PsiDiamondType.getDiamondsAwareResolveResult((PsiCall)gParent);
@@ -836,7 +835,7 @@ public class InferenceSession {
//at this time, types of interface method parameter types must be already calculated
// that's why walkUp in InferenceSessionContainer stops at this point and
//that's why we can reuse this type here
final PsiType cachedLambdaType = LambdaUtil.getFunctionalTypeMap().get(lambdaExpression);
PsiType cachedLambdaType = ThreadLocalTypes.getElementType(lambdaExpression);
if (cachedLambdaType != null) {
return LambdaUtil.getFunctionalInterfaceReturnType(lambdaExpression.getGroundTargetType(cachedLambdaType));
}
@@ -1386,6 +1385,10 @@ public class InferenceSession {
PsiSubstitutor substitutor,
Set<ConstraintFormula> ignoredConstraints) {
formula.apply(substitutor, true);
if (formula instanceof InputOutputConstraintFormula) {
myTempTypes.forceType(((InputOutputConstraintFormula)formula).getExpression(),
((InputOutputConstraintFormula)formula).getCurrentType());
}
addConstraint(formula);
if (!repeatInferencePhases()) {
@@ -1395,7 +1398,7 @@ public class InferenceSession {
if (formula instanceof ExpressionCompatibilityConstraint) {
PsiExpression expression = ((ExpressionCompatibilityConstraint)formula).getExpression();
if (expression instanceof PsiLambdaExpression) {
PsiType parameterType = ((PsiLambdaExpression)expression).getGroundTargetType(((ExpressionCompatibilityConstraint)formula).getT());
PsiType parameterType = ((PsiLambdaExpression)expression).getGroundTargetType(((ExpressionCompatibilityConstraint)formula).getCurrentType());
collectLambdaReturnExpression(additionalConstraints, ignoredConstraints, (PsiLambdaExpression)expression, parameterType, !isProperType(parameterType), substitutor);
}
}
@@ -16,7 +16,6 @@
package com.intellij.psi.impl.source.resolve.graphInference.constraints;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
@@ -32,13 +31,11 @@ import java.util.List;
import java.util.Set;
public class CheckedExceptionCompatibilityConstraint extends InputOutputConstraintFormula {
private static final Logger LOG = Logger.getInstance(CheckedExceptionCompatibilityConstraint.class);
private final PsiExpression myExpression;
private PsiType myT;
public CheckedExceptionCompatibilityConstraint(PsiExpression expression, PsiType t) {
super(t);
myExpression = expression;
myT = t;
}
@Override
@@ -46,6 +43,7 @@ public class CheckedExceptionCompatibilityConstraint extends InputOutputConstrai
if (!PsiPolyExpressionUtil.isPolyExpression(myExpression)) {
return true;
}
PsiType myT = getCurrentType();
if (myExpression instanceof PsiParenthesizedExpression) {
constraints.add(new CheckedExceptionCompatibilityConstraint(((PsiParenthesizedExpression)myExpression).getExpression(), myT));
return true;
@@ -110,9 +108,7 @@ public class CheckedExceptionCompatibilityConstraint extends InputOutputConstrai
final PsiElement body = myExpression instanceof PsiLambdaExpression ? ((PsiLambdaExpression)myExpression).getBody() : myExpression;
if (body != null) {
final List<PsiClassType> exceptions = ExceptionUtil.getUnhandledExceptions(new PsiElement[] {body});
if (exceptions != null) {
thrownTypes.addAll(ContainerUtil.filter(exceptions, type -> !ExceptionUtil.isUncheckedException(type)));
}
thrownTypes.addAll(ContainerUtil.filter(exceptions, type -> !ExceptionUtil.isUncheckedException(type)));
}
if (expectedNonProperThrownTypes.isEmpty()) {
@@ -160,16 +156,6 @@ public class CheckedExceptionCompatibilityConstraint extends InputOutputConstrai
return myExpression;
}
@Override
protected PsiType getT() {
return myT;
}
@Override
protected void setT(PsiType t) {
myT = t;
}
@Override
protected InputOutputConstraintFormula createSelfConstraint(PsiType type, PsiExpression expression) {
return new CheckedExceptionCompatibilityConstraint(expression, type);
@@ -18,15 +18,15 @@ import java.util.Set;
public class ExpressionCompatibilityConstraint extends InputOutputConstraintFormula {
private final PsiExpression myExpression;
private PsiType myT;
public ExpressionCompatibilityConstraint(@NotNull PsiExpression expression, @NotNull PsiType type) {
super(type);
myExpression = expression;
myT = type;
}
@Override
public boolean reduce(InferenceSession session, List<ConstraintFormula> constraints) {
PsiType myT = getCurrentType();
if (!PsiPolyExpressionUtil.isPolyExpression(myExpression)) {
PsiType exprType = myExpression.getType();
@@ -227,16 +227,6 @@ public class ExpressionCompatibilityConstraint extends InputOutputConstraintForm
return myExpression;
}
@Override
public PsiType getT() {
return myT;
}
@Override
protected void setT(PsiType t) {
myT = t;
}
@Override
protected InputOutputConstraintFormula createSelfConstraint(PsiType type, PsiExpression expression) {
return new ExpressionCompatibilityConstraint(expression, type);
@@ -28,10 +28,13 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class InputOutputConstraintFormula implements ConstraintFormula {
private PsiType myT;
protected InputOutputConstraintFormula(PsiType t) {
myT = t;
}
public abstract PsiExpression getExpression();
protected abstract PsiType getT();
protected abstract void setT(PsiType t);
protected abstract InputOutputConstraintFormula createSelfConstraint(PsiType type, PsiExpression expression);
protected abstract void collectReturnTypeVariables(InferenceSession session,
PsiExpression psiExpression,
@@ -40,7 +43,7 @@ public abstract class InputOutputConstraintFormula implements ConstraintFormula
public Set<InferenceVariable> getInputVariables(InferenceSession session) {
final PsiExpression psiExpression = getExpression();
final PsiType type = getT();
final PsiType type = myT;
if (psiExpression instanceof PsiFunctionalExpression) {
final InferenceVariable inferenceVariable = session.getInferenceVariable(type);
if (inferenceVariable != null) {
@@ -111,7 +114,7 @@ public abstract class InputOutputConstraintFormula implements ConstraintFormula
@Nullable
public Set<InferenceVariable> getOutputVariables(Set<InferenceVariable> inputVariables, InferenceSession session) {
final HashSet<InferenceVariable> mentionedVariables = new HashSet<>();
session.collectDependencies(getT(), mentionedVariables);
session.collectDependencies(myT, mentionedVariables);
if (inputVariables != null) {
mentionedVariables.removeAll(inputVariables);
}
@@ -120,14 +123,15 @@ public abstract class InputOutputConstraintFormula implements ConstraintFormula
@Override
public void apply(PsiSubstitutor substitutor, boolean cache) {
setT(substitutor.substitute(getT()));
if (cache) {
LambdaUtil.getFunctionalTypeMap().put(getExpression(), getT());
}
myT = substitutor.substitute(myT);
}
public PsiType getCurrentType() {
return myT;
}
@Override
public String toString() {
return getExpression().getText() + " -> " + getT().getPresentableText();
return getExpression().getText() + " -> " + myT.getPresentableText();
}
}
@@ -161,7 +161,7 @@ public class PsiMethodCallExpressionImpl extends ExpressionPsiElement implements
MethodCandidateInfo.isOverloadCheck(parentArgList) &&
Arrays.stream(parentArgList.getExpressions())
.map(expression -> PsiUtil.skipParenthesizedExprDown(expression))
.noneMatch(expression -> LambdaUtil.getFunctionalTypeMap().containsKey(expression));
.noneMatch(expression -> expression != null && ThreadLocalTypes.hasBindingFor(expression));
PsiType theOnly = null;
for (int i = 0; i < results.length; i++) {
@@ -1,6 +1,6 @@
class IntStream {
private void foo(IntStream s) {
s.<error descr="Ambiguous method call: both 'IntStream.map(IntUnaryOperator)' and 'IntStream.map(ObjIntFunction<Object>)' match">map</error>(i -> 1 << i);
s.<error descr="Ambiguous method call: both 'IntStream.map(IntUnaryOperator)' and 'IntStream.map(ObjIntFunction<Object>)' match">map</error>(i -> <error descr="Operator '<<' cannot be applied to 'int', '<lambda parameter>'">1 << i</error>);
s.<error descr="Ambiguous method call: both 'IntStream.map(IntUnaryOperator)' and 'IntStream.map(ObjIntFunction<Object>)' match">map</error>(i -> 1);
s.<error descr="Ambiguous method call: both 'IntStream.map(IntUnaryOperator)' and 'IntStream.map(ObjIntFunction<Object>)' match">map</error>(i -> i);
}
@@ -14,7 +14,7 @@ class Test {
}
void fooBar(IntStream1 instr){
Supplier<Stream<Integer>> si = () -> instr.<error descr="Ambiguous method call: both 'IntStream1.map(IntFunction<Integer>)' and 'IntStream1.map(IntUnaryOperator)' match">map</error> ((i) -> (( <error descr="Operator '%' cannot be applied to '<lambda parameter>', 'int'">i % 2</error>) == 0) ? i : -i).boxed();
Supplier<Stream<Integer>> si = () -> instr.<error descr="Ambiguous method call: both 'IntStream1.map(IntFunction<Integer>)' and 'IntStream1.map(IntUnaryOperator)' match">map</error> ((i) -> (( <error descr="Operator '%' cannot be applied to '<lambda parameter>', 'int'">i % 2</error>) == 0) ? i : <error descr="Operator '-' cannot be applied to '<lambda parameter>'">-i</error>).boxed();
System.out.println(si);
Supplier<Stream<Integer>> si1 = () -> instr.map <error descr="Ambiguous method call: both 'IntStream1.map(IntFunction<Integer>)' and 'IntStream1.map(IntUnaryOperator)' match">(null)</error>.boxed();
System.out.println(si1);
@@ -14,10 +14,10 @@ abstract class Test {
foo(x -> {
return x += 1;
});
<error descr="Ambiguous method call: both 'Test.foo(A)' and 'Test.foo(B)' match">foo</error>(x -> x += 1);
<error descr="Ambiguous method call: both 'Test.foo(A)' and 'Test.foo(B)' match">foo</error>(x -> <error descr="Incompatible types. Found: 'int', required: '<lambda parameter>'">x += 1</error>);
foo(x -> 1);
foo(x -> <error descr="Operator '!' cannot be applied to 'int'">!x</error>);
<error descr="Ambiguous method call: both 'Test.foo(A)' and 'Test.foo(B)' match">foo</error>(x -> ++x);
<error descr="Ambiguous method call: both 'Test.foo(A)' and 'Test.foo(B)' match">foo</error>(x -> <error descr="Operator '++' cannot be applied to '<lambda parameter>'">++x</error>);
foo(x -> o instanceof String ? 1 : 0);
}
}