diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java
index 349bdec2883..e54e061b503 100644
--- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java
+++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BoundSet.java
@@ -315,7 +315,7 @@ else if (type.hasNullTypeAnnotations())
*
* - captures
- IC18.resumeSuspendedInference() will reset these to avoid captures from nested
* inference spilling blindly into the current inference.
- * {@link InferenceContext18#collectDependencies(BoundSet, boolean, boolean[])} considers only "local" {@link #captures}.
+ * {@link InferenceContext18#collectDependencies(BoundSet)} considers only "local" {@link #captures}.
* - allCaptures
- Still {@link #hasCaptureBound(Set)} and {@link #incorporate(InferenceContext18)} will
* operate on {@link #allCaptures}.
*/
@@ -327,6 +327,7 @@ else if (type.hasNullTypeAnnotations())
private TypeBound[] unincorporatedBounds = new TypeBound[8];
private int unincorporatedBoundsCount = 0;
private final TypeBound[] mostRecentBounds = new TypeBound[4]; // for quick & dirty duplicate elimination
+ public boolean isRecordPatternInference;
public BoundSet() {}
@@ -428,12 +429,17 @@ else if (boundNullBits != 0) // combine bits from both sources, even if this cre
three.setInstantiation(typeBinding, variable, environment);
if (bound.right instanceof InferenceVariable) {
// for a dependency between two IVs make a note about the inverse bound.
- // this should be needed to determine IV dependencies independent of direction.
- // TODO: so far no test could be identified which actually needs it ...
- InferenceVariable rightIV = (InferenceVariable) bound.right.prototype();
- three = this.boundsPerVariable.get(rightIV);
- if (three == null)
- this.boundsPerVariable.put(rightIV, (three = new ThreeSets()));
+ int relation = switch (bound.relation) {
+ case ReductionResult.SUBTYPE -> ReductionResult.SUPERTYPE;
+ case ReductionResult.SUPERTYPE -> ReductionResult.SUBTYPE;
+ case ReductionResult.SAME -> this.isRecordPatternInference ? -1 : ReductionResult.SAME;
+ default -> -1;
+ };
+ if (relation != -1) {
+ InferenceVariable rightIV = (InferenceVariable) bound.right.prototype();
+ three = this.boundsPerVariable.computeIfAbsent(rightIV, k -> new ThreeSets());
+ three.addBound(new TypeBound(rightIV, bound.left, relation, bound.isSoft));
+ }
}
}
}
@@ -600,8 +606,11 @@ boolean incorporate(InferenceContext18 context, TypeBound [] first, TypeBound []
mostRecentFormulas[1] = mostRecentFormulas[0];
mostRecentFormulas[0] = newConstraint;
- if (!reduceOneConstraint(context, newConstraint))
+ if (!reduceOneConstraint(context, newConstraint)) {
+ if (InferenceContext18.DEBUG)
+ System.out.println("Incorporation failed to reduce new constraint "+newConstraint); //$NON-NLS-1$
return false;
+ }
if (analyzeNull) {
// not per JLS: if the new constraint relates types where at least one has a null annotations,
@@ -619,8 +628,11 @@ boolean incorporate(InferenceContext18 context, TypeBound [] first, TypeBound []
}
if (deriveTypeArgumentConstraints) {
for (ConstraintTypeFormula typeArgumentConstraint : deriveTypeArgumentConstraints(bound1, bound2, context)) {
- if (!reduceOneConstraint(context, typeArgumentConstraint))
+ if (!reduceOneConstraint(context, typeArgumentConstraint)) {
+ if (InferenceContext18.DEBUG)
+ System.out.println("Incorporation failed to reduce new constraint "+newConstraint); //$NON-NLS-1$
return false;
+ }
}
}
if (iteration == 2) {
@@ -722,7 +734,8 @@ protected TypeBinding getP(int i) {
if (InferenceContext18.DEBUG) {
if (!capturesToRemove.isEmpty()) {
for (ParameterizedTypeBinding toRemove : capturesToRemove) {
- System.out.println("Removing capture bound " + //$NON-NLS-1$
+ if (this.captures.containsKey(toRemove))
+ System.out.println("Removing capture bound " + //$NON-NLS-1$
String.valueOf(toRemove.shortReadableName()) +
"=capture("+String.valueOf(this.captures.get(toRemove).shortReadableName())+")"); //$NON-NLS-1$ //$NON-NLS-2$
}
@@ -739,11 +752,11 @@ boolean addTypeBoundsFromWildcardBound(InferenceContext18 context, InferenceSubs
ConstraintFormula formula = null;
if (boundKind == Wildcard.EXTENDS) {
if (bi.id == TypeIds.T_JavaLangObject)
- formula = ConstraintTypeFormula.create(t, r, ReductionResult.SUBTYPE);
+ formula = ConstraintTypeFormula.create(t, r, ReductionResult.SUBTYPE, true);
if (t.id == TypeIds.T_JavaLangObject)
- formula = ConstraintTypeFormula.create(theta.substitute(theta, bi), r, ReductionResult.SUBTYPE);
+ formula = ConstraintTypeFormula.create(theta.substitute(theta, bi), r, ReductionResult.SUBTYPE, true);
} else {
- formula = ConstraintTypeFormula.create(theta.substitute(theta, bi), r, ReductionResult.SUBTYPE);
+ formula = ConstraintTypeFormula.create(theta.substitute(theta, bi), r, ReductionResult.SUBTYPE, true);
}
if (formula != null) {
reduceOneConstraint(context, formula);
@@ -875,7 +888,7 @@ private ConstraintTypeFormula combineEqualSupers(TypeBound boundS, TypeBound bou
innerSame = true; // came in as: S REL α and α REL T imply ⟨S REL T⟩
if (outerSame) {
if (innerSame) // NON-JLS bidirectional subtyping implies equality:
- return ConstraintTypeFormula.create(boundS.left, boundS.right, ReductionResult.SAME, false);
+ return ConstraintTypeFormula.create(boundS.left, boundS.right, ReductionResult.SAME, boundT.isSoft||boundS.isSoft);
return ConstraintTypeFormula.create(boundT.left, boundS.right, boundS.relation, boundT.isSoft||boundS.isSoft);
} else if (innerSame) {
return ConstraintTypeFormula.create(boundS.left, boundT.right, boundS.relation, boundT.isSoft||boundS.isSoft);
@@ -1070,6 +1083,32 @@ public boolean hasOnlyTrivialExceptionBounds(InferenceVariable variable, TypeBin
return true;
}
+ public int rankIVar(InferenceVariable ivar) {
+ // implements the ranking of ivars according to their bounds as explained in
+ // https://mail.openjdk.org/archives/list/compiler-dev@openjdk.org/message/GN6RTCGMME6I5JVLSFZRIR32XY6QKOI2/
+ ThreeSets three = this.boundsPerVariable.get(ivar.prototype());
+ if (three != null) {
+ if (three.sameBounds != null)
+ if (!three.sameBounds.isEmpty())
+ return 1;
+ if (this.isRecordPatternInference) {
+ // workaround for not having inverse bounds of type SAME (see addBound(TypeBound, LookupEnvironment)):
+ for (ThreeSets dep3 : this.boundsPerVariable.values()) {
+ if (dep3 != null && dep3.sameBounds != null) {
+ for (TypeBound depBound : dep3.sameBounds) {
+ if (depBound.right.equals(ivar))
+ return 1;
+ }
+ }
+ }
+ }
+ if (three.superBounds != null)
+ if (!three.superBounds.isEmpty())
+ return 2;
+ }
+ return 3;
+ }
+
/**
* JLS 18.1.3:
* Answer all upper bounds for the given inference variable as defined by any bounds in this set.
diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java
index db2db2285d8..a7fbc0f2b36 100644
--- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java
+++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/InferenceContext18.java
@@ -16,6 +16,7 @@
import java.util.*;
import java.util.Map.Entry;
+import java.util.stream.Collectors;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.*;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants.BoundCheckStatus;
@@ -92,8 +93,8 @@
*/
public class InferenceContext18 {
- public final static boolean DEBUG = false;
- public final static boolean DEBUG_FINE = false;
+ public final static boolean DEBUG = Boolean.getBoolean("ecj.typeinference.debug"); //$NON-NLS-1$
+ public final static boolean DEBUG_FINE = Boolean.getBoolean("ecj.typeinference.debug.fine"); //$NON-NLS-1$;
/** NON-JLS: to conform with javac regarding https://bugs.openjdk.java.net/browse/JDK-8026527 */
static final boolean SIMULATE_BUG_JDK_8026527 = true;
@@ -105,12 +106,11 @@ public class InferenceContext18 {
/**
* NON-JLS: Detail flag to control the extent of {@link #SIMULATE_BUG_JDK_8026527}.
- * A setting of 'false' would implement the advice from http://mail.openjdk.java.net/pipermail/lambda-spec-experts/2013-December/000447.html
+ * A setting of 'false' implements the advice from http://mail.openjdk.java.net/pipermail/lambda-spec-experts/2013-December/000447.html
* i.e., raw types are not considered as compatible in constraints/bounds derived from invocation arguments,
* but only for constraints derived from type variable bounds.
- * NON-JLS: Unfortunately, 'true' is required by GenericsRegressionTest.test434118()
*/
- static final boolean ARGUMENT_CONSTRAINTS_ARE_SOFT = true;
+ static final boolean ARGUMENT_CONSTRAINTS_ARE_SOFT = false;
// --- Main State of the Inference: ---
@@ -429,7 +429,7 @@ public BoundSet inferInvocationType(TypeBinding expectedType, InvocationSite inv
return null;
// 5. bullet: determine B4 from C
while (!c.isEmpty()) {
- Map> dependencies = collectDependencies(this.currentBounds, false, new boolean[1]);
+ Map> dependencies = collectDependencies(this.currentBounds);
List> components = new ArrayList<>(dependencies.values());
// *
Set bottomSet = findBottomSet(c, allOutputVariables(c), components);
@@ -1151,13 +1151,6 @@ public boolean reduceAndIncorporate(ConstraintFormula constraint) throws Inferen
private /*@Nullable*/ BoundSet resolve(
InferenceVariable[] toResolve,
boolean isRecordPatternTypeInference) throws InferenceFailureException {
- return resolve(toResolve, isRecordPatternTypeInference, true);
- }
- private /*@Nullable*/ BoundSet resolve(
- InferenceVariable[] toResolve,
- boolean isRecordPatternTypeInference,
- boolean maySkipSuperBound) throws InferenceFailureException {
-
this.captureId = 0;
// NOTE: 18.5.2 ...
// "(While it was necessary to demonstrate that the inference variables in B1 could be resolved
@@ -1168,20 +1161,17 @@ public boolean reduceAndIncorporate(ConstraintFormula constraint) throws Inferen
Set toResolveSet = new LinkedHashSet<>(Arrays.asList(toResolve));
// find a minimal set of dependent variables:
Set variableSet;
- boolean[] hasSkippedSuperBound = { false };
- while ((variableSet = getSmallestVariableSet(tmpBoundSet, toResolveSet, maySkipSuperBound, hasSkippedSuperBound)) != null) {
+ while ((variableSet = getSmallestVariableSet(tmpBoundSet, toResolveSet)) != null) {
int oldNumUninstantiated = tmpBoundSet.numUninstantiatedVariables(this.inferenceVariables);
- final int numVars = variableSet.size();
- if (numVars > 0) {
- final InferenceVariable[] variables = variableSet.toArray(new InferenceVariable[numVars]);
- // NON-JLS: prioritize ivars in 'inThrows' as those may pull in new information by extra rule below (=RuntimeException)
- BoundSet tSet = tmpBoundSet;
- Arrays.sort(variables, (v1, v2) -> {
- int r1 = tSet.inThrows.contains(v1) ? -1 : 0;
- int r2 = tSet.inThrows.contains(v2) ? -1 : 0;
- return r1 - r2;
- });
- //
+ List ofRank;
+ while ((ofRank = pickIvarsByRank(variableSet, tmpBoundSet)) != null) {
+ final int numVars = ofRank.size();
+ if (numVars == 0)
+ continue;
+ if (DEBUG) {
+ System.out.println("Resolving ivars: "+ofRank); //$NON-NLS-1$
+ }
+ final InferenceVariable[] variables = ofRank.toArray(new InferenceVariable[numVars]);
variables: if (!isRecordPatternTypeInference && !tmpBoundSet.hasCaptureBound(variableSet)) {
// try to instantiate this set of variables in a fresh copy of the bound set:
BoundSet prevBoundSet = tmpBoundSet;
@@ -1190,6 +1180,7 @@ public boolean reduceAndIncorporate(ConstraintFormula constraint) throws Inferen
InferenceVariable variable = variables[j];
if (tmpBoundSet.isInstantiated(variable)) { // NON-JLS: may happen when exception bound has been incorporated
toResolveSet.remove(variable);
+ variableSet.remove(variable);
continue;
}
// try lower bounds:
@@ -1236,13 +1227,12 @@ public boolean reduceAndIncorporate(ConstraintFormula constraint) throws Inferen
}
}
toResolveSet.remove(variable);
+ variableSet.remove(variable);
}
if (tmpBoundSet.incorporate(this))
continue;
tmpBoundSet = prevBoundSet;// clean-up for second attempt
}
- if (maySkipSuperBound && hasSkippedSuperBound[0])
- return resolve(toResolve, isRecordPatternTypeInference, false);
// Otherwise, a second attempt is made...
Sorting.sortInferenceVariables(variables); // ensure stability of capture IDs
final CaptureBinding18[] zs = new CaptureBinding18[numVars];
@@ -1278,6 +1268,7 @@ public TypeBinding substitute(TypeVariableBinding typeVariable) {
};
for (int j = 0; j < numVars; j++) {
InferenceVariable variable = variables[j];
+ variableSet.remove(variable);
CaptureBinding18 zsj = zs[j];
// NON-JLS: leverage existing same bounds if they become proper by substitution:
boolean typeboundCreated = false;
@@ -1350,12 +1341,12 @@ public TypeBinding substitute(TypeVariableBinding typeVariable) {
toResolveSet.remove(variable);
}
if (tmpBoundSet.incorporate(this)) {
- if (tmpBoundSet.numUninstantiatedVariables(this.inferenceVariables) == oldNumUninstantiated)
- return null; // abort because we made no progress
continue;
}
return null;
}
+ if (tmpBoundSet.numUninstantiatedVariables(this.inferenceVariables) == oldNumUninstantiated)
+ return null; // abort because we made no progress
}
}
return tmpBoundSet;
@@ -1432,11 +1423,11 @@ public int compare(TypeBinding o1, TypeBinding o2) {
* Find the smallest set of uninstantiated inference variables not depending
* on any uninstantiated variable outside the set.
*/
- public Set getSmallestVariableSet(BoundSet bounds, Set subSet, boolean maySkipSuperBound, boolean[] hasSkippedSuperBound) {
+ public Set getSmallestVariableSet(BoundSet bounds, Set subSet) {
// "Given a set of inference variables to resolve, let V be the union of this set and
// all variables upon which the resolution of at least one variable in this set depends."
Set v = new LinkedHashSet<>(subSet);
- Map> dependencies = collectDependencies(bounds, maySkipSuperBound, hasSkippedSuperBound);
+ Map> dependencies = collectDependencies(bounds);
for (InferenceVariable iv : subSet) {
Set tmp = dependencies.get(iv);
if (tmp != null)
@@ -1458,7 +1449,9 @@ public Set getSmallestVariableSet(BoundSet bounds, Set find a smallest among candidate sets:
if (set == null) {
- return Collections.singleton(currentVariable);
+ set = new HashSet<>();
+ set.add(currentVariable);
+ return set;
}
for (Iterator iter = set.iterator(); iter.hasNext();) {
InferenceVariable iv = iter.next();
@@ -1476,14 +1469,28 @@ public Set getSmallestVariableSet(BoundSet bounds, Set pickIvarsByRank(Set variableSet, BoundSet tmpBoundSet) {
+ // apply the ranking of ivars according to their bounds as explained in
+ // https://mail.openjdk.org/archives/list/compiler-dev@openjdk.org/message/GN6RTCGMME6I5JVLSFZRIR32XY6QKOI2/
+ Map> byRank = variableSet.stream().collect(Collectors.groupingBy(tmpBoundSet::rankIVar));
+ for (int rank = 0; rank < 4; rank++) {
+ List ofRank = byRank.get(rank);
+ if (ofRank == null)
+ continue;
+ final int numVars = ofRank.size();
+ if (numVars == 0)
+ continue;
+ return ofRank;
+ }
+ return null;
+ }
+
/**
* Collect dependencies of all our ivars based on TypeBounds of 'bounds'
* @param bounds consider all its TypeBounds
- * @param maySkipSuperBound if true, then α :> β bounds will not be treated as a dependency from α to β (only the inverse)
- * @param hasSkippedSuperBound output parameter to signal to the caller if maySkipSuperBound has been used
* @return a map from an ivar to the set of all its dependencies including itself.
*/
- Map> collectDependencies(BoundSet bounds, boolean maySkipSuperBound, boolean[] hasSkippedSuperBound) {
+ Map> collectDependencies(BoundSet bounds) {
// Implements the definition of dependencies from JLS §18.4:
Map> dependsOn = new LinkedHashMap<>();
// "An inference variable α depends on the resolution of itself."
@@ -1499,38 +1506,31 @@ Map> collectDependencies(BoundSet bound
// T = α -- encoded as α = T
// T <: α -- encoded as α :> T
for (int i=0; i<2; i++) { // 2 attempts, reading the bound left-to-right, then right-to-left
- if (maySkipSuperBound && typeBound.relation == ReductionResult.SUPERTYPE && typeBound.right instanceof InferenceVariable) {
- // NON-JLS: first try to ignore any dependencies resulting from a supertype bound,
- // i.e., given α :> β do not consider α to depend on β
- hasSkippedSuperBound[0] = true; // signal the application of this tweak to upstream,
- // so they can retry with the tweak disabled (maySkip=false)
- } else {
- Set betas = new LinkedHashSet<>();
- typeBound.right.collectInferenceVariables(betas);
- if (!betas.isEmpty()) {
- InferenceVariable alpha = typeBound.left;
- // Determine the direction of dependencies to add:
- // "If α appears on the left-hand side of another bound of the form G<..., α, ...> = capture(G<...>),
- // then β depends on the resolution of α. Otherwise, α depends on the resolution of β."
- boolean alphaDependsOnBeta = true;
- captureTest: for (ParameterizedTypeBinding gCap : bounds.captures.keySet()) {
- for (TypeBinding arg : gCap.arguments) {
- if (TypeBinding.equalsEquals(arg, alpha)) {
- alphaDependsOnBeta = false;
- break captureTest;
- }
+ Set betas = new LinkedHashSet<>();
+ typeBound.right.collectInferenceVariables(betas);
+ if (!betas.isEmpty()) {
+ InferenceVariable alpha = typeBound.left;
+ // Determine the direction of dependencies to add:
+ // "If α appears on the left-hand side of another bound of the form G<..., α, ...> = capture(G<...>),
+ // then β depends on the resolution of α. Otherwise, α depends on the resolution of β."
+ boolean alphaDependsOnBeta = true;
+ captureTest: for (ParameterizedTypeBinding gCap : bounds.captures.keySet()) {
+ for (TypeBinding arg : gCap.arguments) {
+ if (TypeBinding.equalsEquals(arg, alpha)) {
+ alphaDependsOnBeta = false;
+ break captureTest;
}
}
- if (alphaDependsOnBeta) {
- Set deps = dependsOn.computeIfAbsent(alpha, iv -> new LinkedHashSet<>());
- deps.addAll(betas);
- deps.add(alpha); // add self-dependency, alpha might not yet be recorded if its from inner inference
- } else {
- for (InferenceVariable beta : betas) {
- Set deps = dependsOn.computeIfAbsent(beta, iv -> new LinkedHashSet<>());
- deps.add(alpha);
- deps.add(beta); // add self-dependency, beta might not yet be recorded if its from inner inference
- }
+ }
+ if (alphaDependsOnBeta) {
+ Set deps = dependsOn.computeIfAbsent(alpha, iv -> new LinkedHashSet<>());
+ deps.addAll(betas);
+ deps.add(alpha); // add self-dependency, alpha might not yet be recorded if its from inner inference
+ } else {
+ for (InferenceVariable beta : betas) {
+ Set deps = dependsOn.computeIfAbsent(beta, iv -> new LinkedHashSet<>());
+ deps.add(alpha);
+ deps.add(beta); // add self-dependency, beta might not yet be recorded if its from inner inference
}
}
}
@@ -2095,6 +2095,9 @@ public ReferenceBinding inferRecordPatternParameterization(
TypeVariableBinding[] typeVariables = typeBinding.original().typeVariables();// type para
if (typeVariables == null)
return null;
+ // before adding any bounds signal that we are working on behalf of a record pattern:
+ this.currentBounds = new BoundSet();
+ this.currentBounds.isRecordPatternInference = true;
// An initial bound set, B0, is generated from the declared bounds of P1, ..., Pn,
// as described in 18.1.3.
InferenceVariable[] alphas = createInitialBoundSet(typeVariables); // creates initial bound set B
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_9.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_9.java
index 0af16b1425a..6875bc1db49 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_9.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest_9.java
@@ -2167,6 +2167,26 @@ public List getOriginalList(List list) {
----------
""");
}
+public void testGH4937() {
+ runConformTest(new String[] {
+ "A.java",
+ """
+ import java.util.List;
+
+ public class A {
+ public static void foo() {
+ System.out.println(List.of(BusinessExtractBuilder.create())); // Error here
+ }
+ public static class BusinessExtractBuilder {
+ public static > U create() {
+ return null;
+ }
+ }
+ }
+ """
+ });
+}
+
public static Class testClass() {
return GenericsRegressionTest_9.class;
}