Skip to content

Commit bd4f24a

Browse files
Method call compiles with javac but not ecj with upper bound of type variable (eclipse-jdt#4116)
Fix CaptureBinding.initializeBounds() by more closely adhering to JLS + document correspondence to JLS Fix a bug in glb concerning T#RAW vs T<X> or T<?> + this fix includes making RawTypeBinding.isSubtypeOf() stricter Fixes eclipse-jdt#2523 Co-authored-by: coehlrich <coehlrich@users.noreply.github.com>
1 parent b3f9685 commit bd4f24a

File tree

4 files changed

+93
-32
lines changed

4 files changed

+93
-32
lines changed

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

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*******************************************************************************/
2323
package org.eclipse.jdt.internal.compiler.lookup;
2424

25+
import java.util.Arrays;
2526
import org.eclipse.jdt.core.compiler.CharOperation;
2627
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
2728
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
@@ -203,6 +204,12 @@ public void initializeBounds(Scope scope, ParameterizedTypeBinding capturedParam
203204
}
204205
return;
205206
}
207+
// JLS 5.1.10
208+
// Let G name a generic type declaration (§8.1.2, §9.1.2) with n type parameters A1,...,An with corresponding bounds U1,...,Un.
209+
// our equivalents:
210+
// G = capturedParameterizedType.type
211+
// Ai = wildcardVariable
212+
// Ui = wildcardVariable.superclass & wildcardVariable.superInterfaces()
206213
ReferenceBinding originalVariableSuperclass = wildcardVariable.superclass;
207214
ReferenceBinding substitutedVariableSuperclass = (ReferenceBinding) Scope.substitute(capturedParameterizedType, originalVariableSuperclass);
208215
// prevent cyclic capture: given X<T>, capture(X<? extends T> could yield a circular type
@@ -217,40 +224,62 @@ public void initializeBounds(Scope scope, ParameterizedTypeBinding capturedParam
217224
}
218225
}
219226
// no substitution for wildcard bound (only formal bounds from type variables are to be substituted: 104082)
220-
TypeBinding originalWildcardBound = this.wildcard.bound;
227+
TypeBinding wildcardBound = this.wildcard.bound;
221228

222229
switch (this.wildcard.boundKind) {
223230
case Wildcard.EXTENDS :
224-
// still need to capture bound supertype as well so as not to expose wildcards to the outside (111208)
225-
TypeBinding capturedWildcardBound = originalWildcardBound; // as spec'd
226-
if (originalWildcardBound.isInterface()) {
227-
this.setSuperClass(substitutedVariableSuperclass);
228-
// merge wildcard bound into variable superinterfaces using glb
229-
if (substitutedVariableInterfaces == Binding.NO_SUPERINTERFACES) {
230-
this.setSuperInterfaces(new ReferenceBinding[] { (ReferenceBinding) capturedWildcardBound });
231-
} else {
232-
int length = substitutedVariableInterfaces.length;
233-
System.arraycopy(substitutedVariableInterfaces, 0, substitutedVariableInterfaces = new ReferenceBinding[length+1], 1, length);
234-
substitutedVariableInterfaces[0] = (ReferenceBinding) capturedWildcardBound;
235-
this.setSuperInterfaces(Scope.greaterLowerBound(substitutedVariableInterfaces));
236-
}
237-
} else {
238-
// the wildcard bound should be a subtype of variable superclass
239-
// it may occur that the bound is less specific, then consider glb (202404)
240-
if (capturedWildcardBound.isArrayType() || TypeBinding.equalsEquals(capturedWildcardBound, this)) {
241-
this.setSuperClass(substitutedVariableSuperclass);
242-
} else {
243-
this.setSuperClass((ReferenceBinding) capturedWildcardBound);
244-
if (this.superclass.isSuperclassOf(substitutedVariableSuperclass)) {
245-
this.setSuperClass(substitutedVariableSuperclass);
231+
// JLS:
232+
// If Ti is a wildcard type argument of the form ? extends Bi, then
233+
// Si is a fresh type variable
234+
// - whose upper bound is glb(Bi, Ui[A1:=S1,...,An:=Sn])
235+
// - and whose lower bound is the null type.
236+
// our equivalents:
237+
// Ti = this.wildcard
238+
// Si = this
239+
// Bi = wildcardBound
240+
// substitution [A1:=S1,...,An:=Sn] = substitute(capturedParameterizedType, X)
241+
// upper bound = Bi & subst(wildcardVariable.superclass) & subst(wildcardVariable.superInterfaces)
242+
// = wildcardBound & substitutedVariableSuperclass & substitutedVariableInteraces
243+
// minimize this intersection using glb and distribute it into this.superclass & this.superInterfaces:
244+
TypeBinding[] allBounds;
245+
int ifcLen = substitutedVariableInterfaces.length;
246+
if (wildcardBound instanceof ReferenceBinding) {
247+
allBounds = Arrays.copyOf(substitutedVariableInterfaces, ifcLen + 2);
248+
allBounds[ifcLen+1] = wildcardBound;
249+
} else { // ignoring non-ReferenceBinding wildcardBound (ArrayBinding?), will be set as firstBound only
250+
allBounds = Arrays.copyOf(substitutedVariableInterfaces, ifcLen + 1);
251+
}
252+
allBounds[ifcLen] = substitutedVariableSuperclass;
253+
TypeBinding[] fullGlb = Scope.greaterLowerBound(allBounds, scope, scope.environment());
254+
if (fullGlb != null) {
255+
// optimistically split the fullGlb into one superClass plus n interfaces
256+
ReferenceBinding superClass = scope.getJavaLangObject();
257+
ReferenceBinding[] interfaces = new ReferenceBinding[fullGlb.length];
258+
int j=0;
259+
for (int i=0; i < fullGlb.length; i++) {
260+
if (fullGlb[i] instanceof ReferenceBinding ref) {
261+
if (ref.isInterface())
262+
interfaces[j++] = ref;
263+
else
264+
superClass = ref;
246265
}
247-
// TODO: there are cases were we need to compute glb(capturedWildcardBound, substitutedVariableSuperclass)
248-
// but then when glb (perhaps triggered inside setFirstBound()) fails, how to report the error??
249266
}
250-
this.setSuperInterfaces(substitutedVariableInterfaces);
267+
setSuperClass(superClass);
268+
if (j != fullGlb.length)
269+
interfaces = Arrays.copyOf(interfaces, j);
270+
setSuperInterfaces(interfaces);
271+
} else {
272+
// if glb is null, this capture type is uninhabitable.
273+
// opportunistically use weaker check to determine the stronger bound,
274+
// just so we give more interesting follow-up errors, if any.
275+
if (wildcardBound.original().isCompatibleWith(substitutedVariableSuperclass.original()))
276+
setSuperClass((ReferenceBinding) wildcardBound);
277+
else
278+
setSuperClass(substitutedVariableSuperclass);
279+
setSuperInterfaces(substitutedVariableInterfaces);
251280
}
252-
this.setFirstBound(capturedWildcardBound);
253-
if ((capturedWildcardBound.tagBits & TagBits.HasTypeVariable) == 0)
281+
this.setFirstBound(wildcardBound);
282+
if ((wildcardBound.tagBits & TagBits.HasTypeVariable) == 0)
254283
this.tagBits &= ~TagBits.HasTypeVariable;
255284
break;
256285
case Wildcard.UNBOUND :
@@ -266,19 +295,20 @@ public void initializeBounds(Scope scope, ParameterizedTypeBinding capturedParam
266295
break;
267296
case Wildcard.SUPER :
268297
this.setSuperClass(substitutedVariableSuperclass);
269-
if (TypeBinding.equalsEquals(wildcardVariable.firstBound, substitutedVariableSuperclass) || TypeBinding.equalsEquals(originalWildcardBound, substitutedVariableSuperclass)) {
298+
if (TypeBinding.equalsEquals(wildcardVariable.firstBound, substitutedVariableSuperclass) || TypeBinding.equalsEquals(wildcardBound, substitutedVariableSuperclass)) {
270299
this.setFirstBound(substitutedVariableSuperclass);
271300
}
272301
this.setSuperInterfaces(substitutedVariableInterfaces);
273-
this.lowerBound = originalWildcardBound;
274-
if ((originalWildcardBound.tagBits & TagBits.HasTypeVariable) == 0)
302+
this.lowerBound = wildcardBound;
303+
if ((wildcardBound.tagBits & TagBits.HasTypeVariable) == 0)
275304
this.tagBits &= ~TagBits.HasTypeVariable;
276305
break;
277306
}
278307
if(scope.environment().usesNullTypeAnnotations()) {
279308
evaluateNullAnnotations(scope, null);
280309
}
281310
}
311+
282312
@Override
283313
public TypeBinding upwardsProjection(Scope scope, TypeBinding[] mentionedTypeVariables) {
284314
if (enterRecursiveProjectionFunction()) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ public boolean isProvablyDistinct(TypeBinding otherType) {
218218
public boolean isSubtypeOf(TypeBinding right, boolean simulatingBugJDK8026527) {
219219
if (simulatingBugJDK8026527) {
220220
right = this.environment.convertToRawType(right.erasure(), false);
221+
} else if (!right.isRawType() && TypeBinding.equalsEquals(this.type, right.erasure())) {
222+
return false; // outside the scope of JDK-8026527 T#RAW should not be seen as subtype of T<X> nor T<?>
221223
}
222224
return super.isSubtypeOf(right, simulatingBugJDK8026527);
223225
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ public static TypeBinding[] greaterLowerBound(TypeBinding[] types, /*@Nullable*/
422422
if (isMalformedPair(iType, jType, scope)) {
423423
return null;
424424
}
425-
if (iType.isCompatibleWith(jType, scope)) { // if Vi <: Vj, Vj is removed
425+
if (iType.isSubtypeOf(jType, false)) { // if Vi <: Vj, Vj is removed
426426
if (result == types) { // defensive copy
427427
System.arraycopy(result, 0, result = new TypeBinding[length], 0, length);
428428
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5393,6 +5393,11 @@ public void testBug456459a() {
53935393
" EnumSet<? extends T> set = EnumSet.allOf(enumType);\n" +
53945394
" ^^^^^^^^\n" +
53955395
"Type safety: The expression of type Class needs unchecked conversion to conform to Class<Enum<Enum<E>>>\n" +
5396+
"----------\n" +
5397+
"6. ERROR in EnumTest.java (at line 10)\n" +
5398+
" return set.iterator().next();\n" +
5399+
" ^^^^^^^^^^^^^^^^^^^^^\n" +
5400+
"Type mismatch: cannot convert from capture#1-of ? extends T to T\n" +
53965401
"----------\n");
53975402
}
53985403
// simple conflict introduced by additional wildcard bound
@@ -6737,5 +6742,29 @@ public static void main(String[] args) {
67376742
"----------\n"
67386743
);
67396744
}
6745+
// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2523
6746+
// Compilation error on ecj but not javac due to usage of incorrect super interface
6747+
public void testIssue2523() {
6748+
this.runConformTest(
6749+
new String[] {
6750+
"X.java",
6751+
"""
6752+
public class X {
6753+
public void test() {
6754+
C<? extends D> c = null;
6755+
optional(c, null);
6756+
}
6757+
6758+
private <I extends F, T extends E<I>> void optional(C<T> l, I i) {}
6759+
6760+
public static interface C<T extends E<?>> {}
6761+
public static class D implements E<F> {}
6762+
public static interface E<I extends F> {}
6763+
public static class F {}
6764+
}
6765+
"""
6766+
}
6767+
);
6768+
}
67406769
}
67416770

0 commit comments

Comments
 (0)