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; }