lambda.isValueCompatible should not cache inference results for calls inside lambda body as they may depend on incomplete top level inference where LambdaExpressionCompatibilityConstraint is reduced (IDEA-156311)

This commit is contained in:
Anna.Kozlova
2016-05-20 18:03:15 +02:00
parent 4332c90534
commit ce58f62d19
4 changed files with 151 additions and 0 deletions

View File

@@ -887,6 +887,17 @@ public class InferenceSession {
if (typeTypeByParentCall != null) {
return LambdaUtil.getFunctionalInterfaceReturnType(FunctionalInterfaceParameterizationUtil.getGroundTargetType(typeTypeByParentCall, lambdaExpression));
}
//during checked exception constraint processing
//we may need to infer types for nested calls to infer unhandled exceptions inside lambda body
//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);
if (cachedLambdaType != null) {
return LambdaUtil.getFunctionalInterfaceReturnType(FunctionalInterfaceParameterizationUtil.getGroundTargetType(cachedLambdaType, lambdaExpression));
}
return inferParent || !(PsiUtil.skipParenthesizedExprUp(lambdaExpression.getParent()) instanceof PsiExpressionList)
? LambdaUtil.getFunctionalInterfaceReturnType(lambdaExpression.getFunctionalInterfaceType()) : null;
}

View File

@@ -17,6 +17,7 @@ package com.intellij.psi.impl.source.tree.java;
import com.intellij.icons.AllIcons;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.impl.PsiImplUtil;
@@ -107,6 +108,18 @@ public class PsiLambdaExpressionImpl extends ExpressionPsiElement implements Psi
@Override
public boolean isValueCompatible() {
//it could be called when functional type of lambda expression is not yet defined (during lambda expression compatibility constraint reduction)
//thus inferred results for calls inside could be wrong and should not be cached
final Boolean result = MethodCandidateInfo.ourOverloadGuard.doPreventingRecursion(this, false, new Computable<Boolean>() {
@Override
public Boolean compute() {
return isValueCompatibleNoCache();
}
});
return result != null && result;
}
private boolean isValueCompatibleNoCache() {
final PsiElement body = getBody();
if (body instanceof PsiCodeBlock) {
try {

View File

@@ -0,0 +1,123 @@
import java.util.function.Supplier;
import java.io.IOException;
interface A {
}
interface B extends A {
}
interface D<TT extends Exception> {
<T extends A, E extends TT> T foo() throws E;
}
class E {
void bar(D<RuntimeException> d) {
foobar(supplier(() -> {
try {
return d.foo();
} catch (RuntimeException e) {
throw e;
}
}));
foobar(supplier(() -> {
return d.foo();
}));
foobar(supplier(() -> d.foo()));
foobar(supplier(d::foo));
foobar(supplier(() -> {
throw new RuntimeException();
}));
}
<T> Supplier<T> supplier(Supplier<T> s) {
return s;
}
void foobar(Supplier<B> s) {
}
}
class Test {
interface A {
}
interface B extends A {
}
interface D {
<T extends A> T foo() throws IOException;
}
class E {
void bar(D d) {
foobar(supplier(() -> {
try {
return d.foo();
} catch (IOException e) {
throw new RuntimeException();
}
}));
}
<T> Supplier<T> supplier(Supplier<T> s) {
return s;
}
void foobar(Supplier<B> s) {
}
}
}
class TestNoTypeParameterBounds {
interface A {
}
interface B extends A {
}
interface D {
<T extends A, E extends Throwable> T foo() throws E;
}
class E {
void bar(D d) {
foobar(supplier(() -> {
try {
return d.foo();
} catch (RuntimeException e) {
throw e;
}
}));
foobar(supplier(() -> {
return d.foo();
}));
foobar(supplier(() -> d.foo()));
foobar(supplier(d::foo));
foobar(supplier(() -> {
throw new RuntimeException();
}));
}
<T> Supplier<T> supplier(Supplier<T> s) {
return s;
}
void foobar(Supplier<B> s) {
}
}
}

View File

@@ -324,6 +324,10 @@ public class NewLambdaHighlightingTest extends LightDaemonAnalyzerTestCase {
doTest();
}
public void testExceptionInLambdaBodyCheck() throws Exception {
doTest();
}
private void doTest() {
doTest(false);
}