overload resolution: encapsulate overload guard checks + eliminate additional applicability predicate

GitOrigin-RevId: 41c411466ef8def32fb15535dc639a7f7dc29285
This commit is contained in:
Anna Kozlova
2019-05-06 11:03:23 +03:00
committed by intellij-monorepo-bot
parent c4eb244441
commit e26bb55130
22 changed files with 69 additions and 84 deletions
@@ -1453,7 +1453,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) {
if (results.length == 0 || results[0] instanceof MethodCandidateInfo &&
!((MethodCandidateInfo)results[0]).isApplicable() &&
functionalInterfaceType != null) {
functionalInterfaceType != null || results.length > 1) {
String description = null;
if (results.length == 1) {
description = ((MethodCandidateInfo)results[0]).getInferenceErrorMessage();
@@ -591,6 +591,7 @@ public class ExpectedTypesProvider {
PsiExpression rExpr = assignment.getRExpression();
if (rExpr != null) {
//prevent using left expression to detect type of the right
PsiType type = MethodCandidateInfo.ourOverloadGuard.doPreventingRecursion(assignment, false, () -> rExpr.getType());
if (type != null && type != PsiType.NULL) {
if (type instanceof PsiClassType) {
@@ -1063,13 +1064,11 @@ public class ExpectedTypesProvider {
if (candidateInfo instanceof MethodCandidateInfo) {
final MethodCandidateInfo info = (MethodCandidateInfo)candidateInfo;
Computable<PsiSubstitutor> computable = () -> info.inferSubstitutorFromArgs(policy, args);
substitutor = info.isInferencePossible() && targetMethod == method
? computable.compute()
: MethodCandidateInfo.ourOverloadGuard.doPreventingRecursion(argumentList, false, computable);
substitutor = computable.compute();
if (!info.isStaticsScopeCorrect() && !method.hasModifierProperty(PsiModifier.STATIC) || info.getInferenceErrorMessage() != null) continue;
}
else {
substitutor = MethodCandidateInfo.ourOverloadGuard.doPreventingRecursion(argumentList, false, candidateInfo::getSubstitutor);
substitutor = candidateInfo.getSubstitutor();
}
if (substitutor == null) {
return ExpectedTypeInfo.EMPTY_ARRAY;
@@ -1079,9 +1078,7 @@ public class ExpectedTypesProvider {
if (leftArgs != null && candidateInfo instanceof MethodCandidateInfo) {
Computable<PsiSubstitutor> computable = () -> ((MethodCandidateInfo)candidateInfo).inferSubstitutorFromArgs(policy, leftArgs);
substitutor = ((MethodCandidateInfo)candidateInfo).isInferencePossible() && targetMethod == method
? computable.compute()
: MethodCandidateInfo.ourOverloadGuard.doPreventingRecursion(argumentList, false, computable);
substitutor = computable.compute();
if (substitutor != null) {
inferMethodCallArgumentTypes(argument, forCompletion, leftArgs, index, method, substitutor, set);
if (set.size() >= myMaxCandidates) break;
@@ -21,7 +21,6 @@ import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.UserDataHolder;
@@ -42,7 +41,6 @@ import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.DocumentUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
@@ -486,17 +484,12 @@ public class MethodParameterInfoHandler implements ParameterInfoHandlerWithTabAc
}
private static PsiSubstitutor getCandidateInfoSubstitutor(PsiElement argList, CandidateInfo candidate, boolean resolveResult) {
Computable<PsiSubstitutor> computeSubstitutor =
() -> candidate instanceof MethodCandidateInfo && ((MethodCandidateInfo)candidate).isInferencePossible()
? ((MethodCandidateInfo)candidate).inferTypeArguments(resolveResult ? DefaultParameterTypeInferencePolicy.INSTANCE
: CompletionParameterTypeInferencePolicy.INSTANCE, true)
: candidate.getSubstitutor();
if (resolveResult && candidate instanceof MethodCandidateInfo && ((MethodCandidateInfo)candidate).isInferencePossible()) {
return computeSubstitutor.compute();
}
return MethodCandidateInfo.ourOverloadGuard.doPreventingRecursion(ObjectUtils.notNull(argList, candidate.getElement()),
false,
computeSubstitutor);
return candidate instanceof MethodCandidateInfo &&
((MethodCandidateInfo)candidate).isInferencePossible()
? ((MethodCandidateInfo)candidate)
.inferTypeArguments(resolveResult ? DefaultParameterTypeInferencePolicy.INSTANCE
: CompletionParameterTypeInferencePolicy.INSTANCE, true)
: candidate.getSubstitutor();
}
private static boolean isAssignableParametersBeforeGivenIndex(final PsiParameter[] parms,
@@ -775,9 +775,10 @@ public class LambdaUtil {
if (psiCall == null) {
break;
}
final MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(psiCall.getArgumentList());
PsiExpressionList argumentList = psiCall.getArgumentList();
final MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(argumentList);
if (properties != null) {
if (properties.isApplicabilityCheck() || lambdaExpression != null) {
if (MethodCandidateInfo.isOverloadCheck(argumentList) || lambdaExpression != null) {
break;
}
}
@@ -272,9 +272,11 @@ public class MethodCandidateInfo extends CandidateInfo{
Map<PsiElement, CurrentCandidateProperties> map = CURRENT_CANDIDATE.get();
final PsiElement argumentList = getMarkerList();
final CurrentCandidateProperties alreadyThere =
map.put(argumentList, new CurrentCandidateProperties(this, applicabilityCheck));
map.put(argumentList, new CurrentCandidateProperties(this));
try {
return computable.compute();
return applicabilityCheck
? ourOverloadGuard.doPreventingRecursion(argumentList, false, computable)
: computable.compute();
}
finally {
if (alreadyThere == null) {
@@ -316,16 +318,9 @@ public class MethodCandidateInfo extends CandidateInfo{
myApplicabilityError.remove();
try {
Computable<PsiSubstitutor> computeSubst = () -> inferTypeArguments(DefaultParameterTypeInferencePolicy.INSTANCE, includeReturnConstraint);
final PsiElement markerList = getMarkerList();
final PsiSubstitutor inferredSubstitutor = includeReturnConstraint || markerList == null || isOverloadCheck()
? computeSubst.compute()
: ourOverloadGuard.doPreventingRecursion(markerList, true, computeSubst);
if (inferredSubstitutor == null) {
//todo log error
return mySubstitutor;
}
final PsiSubstitutor inferredSubstitutor = inferTypeArguments(DefaultParameterTypeInferencePolicy.INSTANCE, includeReturnConstraint);
if (!stackStamp.mayCacheNow() ||
isOverloadCheck() ||
!includeReturnConstraint && myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8) ||
@@ -369,6 +364,9 @@ public class MethodCandidateInfo extends CandidateInfo{
return !ourOverloadGuard.currentStack().isEmpty();
}
public static boolean isOverloadCheck(PsiElement argumentList) {
return ourOverloadGuard.currentStack().contains(argumentList);
}
public boolean isTypeArgumentsApplicable() {
return isTypeArgumentsApplicable(() -> getSubstitutor(false));
@@ -494,16 +492,14 @@ public class MethodCandidateInfo extends CandidateInfo{
}
public CurrentCandidateProperties createProperties() {
return new CurrentCandidateProperties(this, false);
return new CurrentCandidateProperties(this);
}
public static class CurrentCandidateProperties {
private final MethodCandidateInfo myMethod;
private final boolean myApplicabilityCheck;
private CurrentCandidateProperties(MethodCandidateInfo info, boolean applicabilityCheck) {
private CurrentCandidateProperties(MethodCandidateInfo info) {
myMethod = info;
myApplicabilityCheck = applicabilityCheck;
}
public PsiMethod getMethod() {
@@ -513,11 +509,6 @@ public class MethodCandidateInfo extends CandidateInfo{
public MethodCandidateInfo getInfo() {
return myMethod;
}
public boolean isApplicabilityCheck() {
return myApplicabilityCheck;
}
}
public static class ApplicabilityLevel {
@@ -169,17 +169,22 @@ public class InferenceSession {
}
}
}
private static MethodCandidateInfo.CurrentCandidateProperties getCurrentProperties(PsiElement parent) {
private static PsiExpressionList getArgumentList(PsiElement parent) {
if (parent instanceof PsiCall) {
return MethodCandidateInfo.getCurrentMethod(((PsiCall)parent).getArgumentList());
return ((PsiCall)parent).getArgumentList();
}
if (parent instanceof PsiAnonymousClass) {
return getCurrentProperties(parent.getParent());
return getArgumentList(parent.getParent());
}
return null;
}
private static MethodCandidateInfo.CurrentCandidateProperties getCurrentProperties(PsiElement parent) {
PsiExpressionList argumentList = getArgumentList(parent);
return argumentList != null ? MethodCandidateInfo.getCurrentMethod(argumentList) : null;
}
/**
* Definition from 15.12.2.2 Phase 1: Identify Matching Arity Methods Applicable by Subtyping Strict Invocation
* An argument expression is considered pertinent to applicability for a potentially-applicable method m unless it has one of the following forms:
@@ -336,7 +341,8 @@ public class InferenceSession {
return;
}
if (properties != null && !properties.isApplicabilityCheck()) {
PsiExpressionList argumentList = getArgumentList(parent);
if (properties != null && argumentList != null && !MethodCandidateInfo.isOverloadCheck(argumentList)) {
String expectedActualErrorMessage = null;
final PsiMethod method = properties.getMethod();
if (parent instanceof PsiCallExpression && PsiPolyExpressionUtil.isMethodCallPolyExpression((PsiExpression)parent, method)) {
@@ -365,7 +371,7 @@ public class InferenceSession {
}
}
//proceed to B3 constraints
else if (parameters != null && parameters.length > 0 && args != null && !isOverloadCheck()) {
else if (parameters != null && parameters.length > 0 && args != null && !isOverloadCheck()) {//todo
final Set<ConstraintFormula> additionalConstraints = new LinkedHashSet<>();
final HashSet<ConstraintFormula> ignoredConstraints = new HashSet<>();
collectAdditionalConstraints(parameters, args, properties.getMethod(), mySiteSubstitutor, additionalConstraints,
@@ -830,7 +836,7 @@ public class InferenceSession {
final PsiExpressionList argumentList = ((PsiCall)gParent).getArgumentList();
if (argumentList != null) {
final MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(argumentList);
if (properties != null && properties.isApplicabilityCheck()) {
if (properties != null && MethodCandidateInfo.isOverloadCheck(argumentList)) {
return getTypeByMethod(context, argumentList, properties.getMethod(), properties.getInfo().isVarargs(), properties.getInfo().getSiteSubstitutor(), inferParent);
}
@@ -1118,7 +1124,7 @@ public class InferenceSession {
}
public void setUncheckedInContext() {
if (myContext != null && !MethodCandidateInfo.isOverloadCheck()) {
if (myContext != null && !MethodCandidateInfo.isOverloadCheck(getArgumentList(myContext))) {//todo
myContext.putUserData(ERASED, myErased);
}
}
@@ -59,7 +59,7 @@ public class InferenceSessionContainer {
if (//in order to to avoid caching of candidates's errors on parent (!) , so check for overload resolution is left here
//But overload resolution can depend on type of lambda parameter. As it can't depend on lambda body,
//traversing down would stop at lambda level and won't take into account overloaded method
!MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argumentList)) {
!MethodCandidateInfo.isOverloadCheck(argumentList)) {
final PsiCall topLevelCall = PsiResolveHelper.ourGraphGuard.doPreventingRecursion(parent, false,
() -> {
if (parent instanceof PsiExpression && !PsiPolyExpressionUtil.isPolyExpression((PsiExpression)parent)) {
@@ -68,7 +68,7 @@ public class PsiConditionalExpressionImpl extends ExpressionPsiElement implement
if (PsiUtil.isLanguageLevel8OrHigher(this) &&
PsiPolyExpressionUtil.isPolyExpression(this) &&
!MethodCandidateInfo.ourOverloadGuard.currentStack().contains(PsiUtil.skipParenthesizedExprUp(this.getParent()))) {
!MethodCandidateInfo.isOverloadCheck(PsiUtil.skipParenthesizedExprUp(this.getParent()))) {
//15.25.3 Reference Conditional Expressions
// The type of a poly reference conditional expression is the same as its target type.
return InferenceSession.getTargetType(this);
@@ -176,7 +176,7 @@ public class PsiLambdaExpressionImpl extends JavaStubPsiElement<FunctionalExpres
}
final PsiExpressionList argsList = PsiTreeUtil.getParentOfType(this, PsiExpressionList.class);
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList)) {
if (MethodCandidateInfo.isOverloadCheck(argsList)) {
final MethodCandidateInfo.CurrentCandidateProperties candidateProperties = MethodCandidateInfo.getCurrentMethod(argsList);
if (candidateProperties != null) {
final PsiMethod method = candidateProperties.getMethod();
@@ -195,7 +195,7 @@ public class PsiLambdaExpressionImpl extends JavaStubPsiElement<FunctionalExpres
return false;
}
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList) && !hasFormalParameterTypes()) {
if (MethodCandidateInfo.isOverloadCheck(argsList) && !hasFormalParameterTypes()) {
return true;
}
@@ -382,7 +382,7 @@ public class PsiMethodReferenceExpressionImpl extends JavaStubPsiElement<Functio
final PsiExpressionList argsList = PsiTreeUtil.getParentOfType(this, PsiExpressionList.class);
final boolean isExact = isExact();
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList)) {
if (MethodCandidateInfo.isOverloadCheck(argsList)) {
final MethodCandidateInfo.CurrentCandidateProperties candidateProperties = MethodCandidateInfo.getCurrentMethod(argsList);
if (candidateProperties != null) {
final PsiMethod method = candidateProperties.getMethod();
@@ -401,7 +401,7 @@ public class PsiMethodReferenceExpressionImpl extends JavaStubPsiElement<Functio
return false;
}
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList)) {
if (MethodCandidateInfo.isOverloadCheck(argsList)) {
if (!isExact) {
return true;
}
@@ -33,7 +33,7 @@ public class PsiSwitchExpressionImpl extends PsiSwitchBlockImpl implements PsiSw
@Override
public PsiType getType() {
if (PsiPolyExpressionUtil.isPolyExpression(this) &&
!MethodCandidateInfo.ourOverloadGuard.currentStack().contains(PsiUtil.skipParenthesizedExprUp(getParent()))) {
!MethodCandidateInfo.isOverloadCheck(PsiUtil.skipParenthesizedExprUp(getParent()))) {
return InferenceSession.getTargetType(this);
}
@@ -48,11 +48,11 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{
@Override
public final CandidateInfo resolveConflict(@NotNull final List<CandidateInfo> conflicts){
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(myArgumentsList)) {
if (MethodCandidateInfo.isOverloadCheck(myArgumentsList)) {
LOG.error("Recursive conflict resolution for:" + myArgumentsList.getParent() + "; " +
"file=" + myArgumentsList.getContainingFile());
}
return MethodCandidateInfo.ourOverloadGuard.doPreventingRecursion(myArgumentsList, false, () -> guardedOverloadResolution(conflicts));
return guardedOverloadResolution(conflicts);
}
@Nullable
@@ -26,7 +26,7 @@ class AlienTest {
IInt i1 = MyTest::<error descr="Cannot resolve method 'abracadabra'">abracadabra</error>;
IInt i2 = MyTest::<error descr="Incompatible types: int is not convertible to String">foo</error>;
IInt i3 = MyTest::<error descr="Cannot resolve method 'bar'">bar</error>;
<error descr="Incompatible types. Found: '<method reference>', required: 'AlienTest.IIntInt'">IIntInt i4 = MyTest::bar;</error>
IIntInt i4 = MyTest::<error descr="Cannot resolve method 'bar'">bar</error>;
IInt i5 = <error descr="Non-static method cannot be referenced from a static context">MyTest::baz</error>;
IInt i6 = <error descr="'foo(int)' is not public in 'MyTest.Foo'. Cannot be accessed from outside package">MyTest.foo::foo</error>;
IInt i7 = MyTest.<error descr="'MyTest.Foo' has private access in 'MyTest'">Foo</error>::foo;
@@ -22,7 +22,7 @@ class MyTest {
{
Bar1 b1 = MyTest :: foo;
bar<error descr="Ambiguous method call: both 'MyTest.bar(Bar1)' and 'MyTest.bar(Bar2)' match">(MyTest :: foo)</error>;
bar(MyTest :: <error descr="Cannot resolve method 'foo'">foo</error>);
}
}
@@ -71,7 +71,7 @@ class StaticInner1 {
static void call3(I2 s) {}
static {
call3<error descr="Ambiguous method call: both 'StaticInner1.call3(I1)' and 'StaticInner1.call3(I2)' match">(StaticInner1.Inner :: new)</error>;
call3(StaticInner1.Inner :: <error descr="Cannot resolve constructor 'Inner'">new</error>);
}
}
@@ -6,7 +6,7 @@ interface Func<TIn, TOut>{
class Main {
public static void main(final String[] args) {
<error descr="Incompatible types. Found: '<method reference>', required: 'Func<java.lang.Integer,java.lang.String>'">Func<Integer, String> func = Integer::toString;</error>
Func<Integer, String> func = Integer::<error descr="Cannot resolve method 'toString'">toString</error>;
System.out.println(func.run(6));
}
}
@@ -2,7 +2,7 @@ import java.util.*;
class LambdaTest {
public void testR() {
<error descr="Incompatible types. Found: 'java.lang.String', required: '<method reference>'">new ArrayList<String>() :: size = ""</error>;
new ArrayList<String>() :: <error descr="Cannot resolve method 'size'">size</error> = "";
}
}
@@ -57,10 +57,10 @@ class MyTest1 {
I2 s2 = MyTest1 :: m2;
call2(MyTest1::m2);
<error descr="Incompatible types. Found: '<method reference>', required: 'MyTest1.I2'">I2 s3 = MyTest1 :: m3;</error>
call2<error descr="'call2(MyTest1.I2)' in 'MyTest1' cannot be applied to '(<method reference>)'">(MyTest1::m3)</error>;
<error descr="Incompatible types. Found: '<method reference>', required: 'MyTest1.I2'">I2 s4 = MyTest1 ::m4;</error>
call2<error descr="'call2(MyTest1.I2)' in 'MyTest1' cannot be applied to '(<method reference>)'">(MyTest1::m4)</error>;
I2 s3 = MyTest1 :: <error descr="Cannot resolve method 'm3'">m3</error>;
call2(MyTest1::<error descr="Cannot resolve method 'm3'">m3</error>);
I2 s4 = MyTest1 ::<error descr="Cannot resolve method 'm4'">m4</error>;
call2(MyTest1::<error descr="Cannot resolve method 'm4'">m4</error>);
}
}
@@ -106,19 +106,19 @@ class MyTest2 {
I2 s2 = MyTest2 :: m2;
call2(MyTest2::m2);
<error descr="Incompatible types. Found: '<method reference>', required: 'MyTest2.I2'">I2 s3 = MyTest2 :: m3;</error>
call2<error descr="'call2(MyTest2.I2)' in 'MyTest2' cannot be applied to '(<method reference>)'">(MyTest2::m3)</error>;
<error descr="Incompatible types. Found: '<method reference>', required: 'MyTest2.I2'">I2 s4 = MyTest2 ::m4;</error>
call2<error descr="'call2(MyTest2.I2)' in 'MyTest2' cannot be applied to '(<method reference>)'">(MyTest2::m4)</error>;
I2 s3 = MyTest2 :: <error descr="Cannot resolve method 'm3'">m3</error>;
call2(MyTest2::<error descr="Cannot resolve method 'm3'">m3</error>);
I2 s4 = MyTest2 ::<error descr="Cannot resolve method 'm4'">m4</error>;
call2(MyTest2::<error descr="Cannot resolve method 'm4'">m4</error>);
}
static void call3(I1 s) {}
static void call3(I2 s) {}
static {
call3(MyTest2::m1);
call3<error descr="Ambiguous method call: both 'MyTest2.call3(I1)' and 'MyTest2.call3(I2)' match">(MyTest2::m2)</error>;
call3(MyTest2::<error descr="Cannot resolve method 'm2'">m2</error>);
call3(MyTest2::m3);
call3<error descr="'call3(MyTest2.I2)' in 'MyTest2' cannot be applied to '(<method reference>)'">(MyTest2::m4)</error>;
call3(MyTest2::<error descr="Cannot resolve method 'm4'">m4</error>);
}
}
@@ -31,8 +31,8 @@ class Test {
String i1 = instanceCall(this::m0);
String i2 = instanceCall(this::m1);
String i3 = instanceCall(this::m2);
String i4 = instanceCall<error descr="Ambiguous method call: both 'Test.instanceCall(I0)' and 'Test.instanceCall(I1<Object>)' match">(this::m01)</error>;
String i5 = instanceCall<error descr="Ambiguous method call: both 'Test.instanceCall(I0)' and 'Test.instanceCall(I1<Object>)' match">(this::m012)</error>;
String i4 = instanceCall(this::<error descr="Cannot resolve method 'm01'">m01</error>);
String i5 = instanceCall(this::<error descr="Cannot resolve method 'm012'">m012</error>);
}
void n0() { }
@@ -53,7 +53,7 @@ class Test {
Test s1 = staticCall(Test::n0);
Test s2 = staticCall(Test::n1);
Test s3 = staticCall<error descr="Cannot resolve method 'staticCall(<method reference>)'">(Test::n2)</error>;
Test s4 = staticCall<error descr="Ambiguous method call: both 'Test.staticCall(I1<Object>)' and 'Test.staticCall(I2<Object, String>)' match">(Test::n01)</error>;
Test s5 = staticCall<error descr="Ambiguous method call: both 'Test.staticCall(I1<Object>)' and 'Test.staticCall(I2<Object, String>)' match">(Test::n012)</error>;
Test s4 = staticCall(Test::<error descr="Cannot resolve method 'n01'">n01</error>);
Test s5 = staticCall(Test::<error descr="Cannot resolve method 'n012'">n012</error>);
}
}
@@ -34,7 +34,7 @@ class Test {
{
Set<String> m = replyWith(this::query);
System.out.println(m);
Set<String> m1 = replyWith<error descr="Ambiguous method call: both 'Test.replyWith(Function<Object, List<Object>>)' and 'Test.replyWith(Callable<List<Object>>)' match">(this::query1)</error>;
Set<String> m1 = replyWith(this::<error descr="Cannot resolve method 'query1'">query1</error>);
System.out.println(m1);
}
}
@@ -10,6 +10,6 @@ class Test {
static void m(Test t, Object s) {}
static void test() {
<error descr="Incompatible types. Found: '<method reference>', required: 'Test.I'">I i = Test::m;</error>
I i = Test::<error descr="Cannot resolve method 'm'">m</error>;
}
}
@@ -84,10 +84,7 @@ public class NewMethodRefHighlightingTest extends LightDaemonAnalyzerTestCase {
doHighlighting()
.stream()
.filter(info -> info.type == HighlightInfoType.ERROR)
.forEach(info -> Assert.assertEquals("<html><body>Incompatible types." +
"<table><tr><td>Required:</td><td><font color='red'><b>Test.I</b></font></td></tr><tr><td>" +
"Found:</td><td><font color='red'><b>&lt;method reference&gt;</b></font></td></tr></table><br/>" +
"reason: method reference is ambiguous: both 'Test.m(Test, String)' and 'Test.m(String)' match</body></html>",
.forEach(info -> Assert.assertEquals("<html>Cannot resolve method 'm'</html>",
info.getToolTip()));
}
public void testSuperClassPotentiallyApplicableMembers() { doTest(); }