Skip to content

Commit 2d39457

Browse files
ECJ fails to compile method call with 2 method references with cycling
generics whilst javac succeeds + implement IC18.pickFromCycle with fresh interpretation of JLS (25) Fixes eclipse-jdt#4346
1 parent 0df22c9 commit 2d39457

File tree

2 files changed

+55
-89
lines changed

2 files changed

+55
-89
lines changed

org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java

Lines changed: 33 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,19 +1408,15 @@ private void addDependencies(BoundSet boundSet, Set<InferenceVariable> variableS
14081408
}
14091409

14101410
private ConstraintFormula pickFromCycle(Set<ConstraintFormula> c) {
1411-
// Detail from 18.5.2 bullet 6.1
1412-
14131411
// Note on performance: this implementation could quite possibly be optimized a lot.
14141412
// However, we only *very rarely* reach here,
14151413
// so nobody should really be affected by the performance penalty paid here.
14161414

1417-
// Note on spec conformance: the spec seems to require _all_ criteria (i)-(iv) to be fulfilled
1418-
// with the sole exception of (iii), which should only be used, if _any_ constraints matching (i) & (ii)
1419-
// also fulfill this condition.
1420-
// Experiments, however, show that strict application of the above is prone to failing to pick any constraint,
1421-
// causing non-termination of the algorithm.
1422-
// Since that is not acceptable, I'm *interpreting* the spec to request a search for a constraint
1423-
// that "best matches" the given conditions.
1415+
// from JLS 18.5.2.2 bullet 3.1 para 2:
1416+
//
1417+
// If this subset is empty, then there is a cycle (or cycles) in the graph of dependencies between constraints.
1418+
// In this case, the constraints in C that participate in a dependency cycle (or cycles) and
1419+
// do not depend on any constraints outside of the cycle (or cycles) are considered.
14241420

14251421
// collect all constraints participating in a cycle
14261422
HashMap<ConstraintFormula,Set<ConstraintFormula>> dependencies = new HashMap<>();
@@ -1448,10 +1444,10 @@ private ConstraintFormula pickFromCycle(Set<ConstraintFormula> c) {
14481444
outside.removeAll(cycles);
14491445

14501446
Set<ConstraintFormula> candidatesII = new LinkedHashSet<>();
1451-
// (i): participates in a cycle:
1447+
// participates in a cycle:
14521448
candidates: for (ConstraintFormula candidate : cycles) {
14531449
Collection<InferenceVariable> infVars = candidate.inputVariables(this);
1454-
// (ii) does not depend on any constraints outside the cycle
1450+
// does not depend on any constraints outside the cycle
14551451
for (ConstraintFormula out : outside) {
14561452
if (dependsOn(infVars, out.outputVariables(this)))
14571453
continue candidates;
@@ -1461,62 +1457,39 @@ private ConstraintFormula pickFromCycle(Set<ConstraintFormula> c) {
14611457
if (candidatesII.isEmpty())
14621458
candidatesII = c; // not spec'ed but needed to avoid returning null below, witness: java.util.stream.Collectors
14631459

1464-
// tentatively: (iii) has the form ⟨Expression → T⟩
1465-
Set<ConstraintFormula> candidatesIII = new LinkedHashSet<>();
1460+
// JLS cntd:
1461+
// A single constraint is selected from these considered constraints, as follows:
1462+
// * If any of the considered constraints have the form ‹Expression → T›, then
1463+
// the selected constraint is the considered constraint of this form that contains the expression
1464+
// to the left (§3.5) of the expression of every other considered constraint of this form.
1465+
int minStart = Integer.MAX_VALUE;
1466+
ConstraintFormula leftMost = null;
14661467
for (ConstraintFormula candidate : candidatesII) {
1467-
if (candidate instanceof ConstraintExpressionFormula)
1468-
candidatesIII.add(candidate);
1469-
}
1470-
if (candidatesIII.isEmpty()) {
1471-
candidatesIII = candidatesII; // no constraint fulfills (iii) -> ignore this condition
1472-
} else { // candidatesIII contains all relevant constraints ⟨Expression → T⟩
1473-
// (iv) contains an expression that appears to the left of the expression
1474-
// of every other constraint satisfying the previous three requirements
1475-
1476-
// collect containment info regarding all expressions in candidate constraints:
1477-
// (a) find minimal enclosing expressions:
1478-
Map<ConstraintExpressionFormula,ConstraintExpressionFormula> expressionContainedBy = new LinkedHashMap<>();
1479-
for (ConstraintFormula one : candidatesIII) {
1480-
ConstraintExpressionFormula oneCEF = (ConstraintExpressionFormula) one;
1481-
Expression exprOne = oneCEF.left;
1482-
for (ConstraintFormula two : candidatesIII) {
1483-
if (one == two) continue;
1484-
ConstraintExpressionFormula twoCEF = (ConstraintExpressionFormula) two;
1485-
Expression exprTwo = twoCEF.left;
1486-
if (doesExpressionContain(exprOne, exprTwo)) {
1487-
ConstraintExpressionFormula previous = expressionContainedBy.get(two);
1488-
if (previous == null || doesExpressionContain(previous.left, exprOne)) // only if improving
1489-
expressionContainedBy.put(twoCEF, oneCEF);
1490-
}
1491-
}
1492-
}
1493-
// (b) build the tree from the above
1494-
Map<ConstraintExpressionFormula,Set<ConstraintExpressionFormula>> containmentForest = new LinkedHashMap<>();
1495-
for (Map.Entry<ConstraintExpressionFormula, ConstraintExpressionFormula> parentRelation : expressionContainedBy.entrySet()) {
1496-
ConstraintExpressionFormula parent = parentRelation.getValue();
1497-
Set<ConstraintExpressionFormula> children = containmentForest.get(parent);
1498-
if (children == null)
1499-
containmentForest.put(parent, children = new LinkedHashSet<>());
1500-
children.add(parentRelation.getKey());
1468+
if (candidate instanceof ConstraintExpressionFormula cef && cef.left.sourceStart < minStart) {
1469+
minStart = cef.left.sourceStart;
1470+
leftMost = cef;
15011471
}
1472+
}
1473+
if (leftMost != null)
1474+
return leftMost;
15021475

1503-
// approximate the spec by searching the largest containment tree:
1504-
int bestRank = -1;
1505-
ConstraintExpressionFormula candidate = null;
1506-
for (ConstraintExpressionFormula parent : containmentForest.keySet()) {
1507-
int rank = rankNode(parent, expressionContainedBy, containmentForest);
1508-
if (rank > bestRank) {
1509-
bestRank = rank;
1510-
candidate = parent;
1511-
}
1476+
// JLS cntd:
1477+
// * If no considered constraint has the form ‹Expression → T›, then the selected constraint is the considered
1478+
// constraint that contains the expression to the left of the expression of every other considered constraint.
1479+
for (ConstraintFormula candidate : candidatesII) {
1480+
// note: ConstraintExpressionFormula is the only shape left, which contains an expression
1481+
if (candidate instanceof ConstraintExceptionFormula cef && cef.left.sourceStart < minStart) {
1482+
minStart = cef.left.sourceStart;
1483+
leftMost = cef;
15121484
}
1513-
if (candidate != null)
1514-
return candidate;
15151485
}
1486+
if (leftMost != null)
1487+
return leftMost;
15161488

1517-
if (candidatesIII.isEmpty())
1489+
// not spec'ed: random pick
1490+
if (candidatesII.isEmpty())
15181491
throw new IllegalStateException("cannot pick constraint from cyclic set"); //$NON-NLS-1$
1519-
return candidatesIII.iterator().next();
1492+
return candidatesII.iterator().next();
15201493
}
15211494

15221495
/**
@@ -1554,35 +1527,6 @@ private boolean isReachable(Map<ConstraintFormula,Set<ConstraintFormula>> deps,
15541527
return false;
15551528
}
15561529

1557-
/** Does exprOne lexically contain exprTwo? */
1558-
private boolean doesExpressionContain(Expression exprOne, Expression exprTwo) {
1559-
if (exprTwo.sourceStart > exprOne.sourceStart) {
1560-
return exprTwo.sourceEnd <= exprOne.sourceEnd;
1561-
} else if (exprTwo.sourceStart == exprOne.sourceStart) {
1562-
return exprTwo.sourceEnd < exprOne.sourceEnd;
1563-
}
1564-
return false;
1565-
}
1566-
1567-
/** non-roots answer -1, roots answer the size of the spanned tree */
1568-
private int rankNode(ConstraintExpressionFormula parent,
1569-
Map<ConstraintExpressionFormula,ConstraintExpressionFormula> expressionContainedBy,
1570-
Map<ConstraintExpressionFormula, Set<ConstraintExpressionFormula>> containmentForest)
1571-
{
1572-
if (expressionContainedBy.get(parent) != null)
1573-
return -1; // not a root
1574-
Set<ConstraintExpressionFormula> children = containmentForest.get(parent);
1575-
if (children == null)
1576-
return 1; // unconnected node or leaf
1577-
int sum = 1;
1578-
for (ConstraintExpressionFormula child : children) {
1579-
int cRank = rankNode(child, expressionContainedBy, containmentForest);
1580-
if (cRank > 0)
1581-
sum += cRank;
1582-
}
1583-
return sum;
1584-
}
1585-
15861530
private Set<ConstraintFormula> findBottomSet(Set<ConstraintFormula> constraints,
15871531
Set<InferenceVariable> allOutputVariables, List<Set<InferenceVariable>> components)
15881532
{

org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_9.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,28 @@ public static abstract class AbstractJsonNodeMatcher<A extends JsonNode> impleme
15591559
},
15601560
"");
15611561
}
1562+
public void testGH4346() {
1563+
runConformTest(new String[] {
1564+
"X.java",
1565+
"""
1566+
import java.util.function.Function;
1567+
public class X {
1568+
public static <T> W1<W2<T>> main(String[] args) {
1569+
return m(m2(), W2::t, W2::new);
1570+
}
1571+
1572+
public static <T1, T2> W1<T1> m(W1<T2> w1, Function<T1, T2> f1, Function<T2, T1> f2) {
1573+
return null;
1574+
}
1575+
private static <T> W1<W1<T>> m2() {
1576+
return null;
1577+
}
1578+
private record W1<T>(T t) {}
1579+
private record W2<T>(W1<T> t) {}
1580+
}
1581+
"""
1582+
});
1583+
}
15621584
public static Class<GenericsRegressionTest_9> testClass() {
15631585
return GenericsRegressionTest_9.class;
15641586
}

0 commit comments

Comments
 (0)