Skip to content

Commit 8c812e2

Browse files
preserve null info from erased bound '? extends @nonnull Object'
1 parent a23155a commit 8c812e2

4 files changed

Lines changed: 83 additions & 51 deletions

File tree

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

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -188,18 +188,11 @@ public WildcardBinding getWildcard(ReferenceBinding genericType, int rank, TypeB
188188
if (genericType.hasTypeAnnotations())
189189
throw new IllegalStateException();
190190

191-
checkUnbounded: if (boundKind == Wildcard.EXTENDS && bound != null && bound.id == TypeIds.T_JavaLangObject) {
192-
if ((bound.tagBits & TagBits.AnnotationNullMASK) != 0)
193-
break checkUnbounded;
194-
if (otherBounds != null) {
195-
for (TypeBinding otherBound : otherBounds) {
196-
if ((otherBound.tagBits & TagBits.AnnotationNullMASK) != 0)
197-
break checkUnbounded;
198-
}
199-
}
191+
long objectBoundNullTagBits = 0;
192+
if (boundKind == Wildcard.EXTENDS && bound != null && bound.id == TypeIds.T_JavaLangObject && otherBounds == null) {
193+
objectBoundNullTagBits = bound.tagBits & TagBits.AnnotationNullMASK;
200194
boundKind = Wildcard.UNBOUND;
201195
bound = null;
202-
otherBounds = null;
203196
}
204197

205198
WildcardBinding nakedType = null;
@@ -212,20 +205,22 @@ public WildcardBinding getWildcard(ReferenceBinding genericType, int rank, TypeB
212205
continue;
213206
if (derivedType.boundKind() != boundKind || derivedType.bound() != bound || !Util.effectivelyEqual(derivedType.additionalBounds(), otherBounds)) //$IDENTITY-COMPARISON$
214207
continue;
215-
if (Util.effectivelyEqual(derivedType.getTypeAnnotations(), annotations))
216-
return (WildcardBinding) derivedType;
208+
WildcardBinding derivedWildcard = (WildcardBinding) derivedType;
209+
if (Util.effectivelyEqual(derivedType.getTypeAnnotations(), annotations) && derivedWildcard.hasNullTagBits(objectBoundNullTagBits))
210+
return derivedWildcard;
217211
if (!derivedType.hasTypeAnnotations())
218-
nakedType = (WildcardBinding) derivedType;
212+
nakedType = derivedWildcard;
219213
}
220214

221215
if (nakedType == null)
222216
nakedType = super.getWildcard(genericType, rank, bound, otherBounds, boundKind);
223217

224-
if (!haveTypeAnnotations(genericType, bound, otherBounds, annotations))
218+
if (!haveTypeAnnotations(genericType, bound, otherBounds, annotations) && objectBoundNullTagBits == 0)
225219
return nakedType;
226220

227221
WildcardBinding wildcard = new WildcardBinding(genericType, rank, bound, otherBounds, boundKind, this.environment);
228222
wildcard.id = nakedType.id;
223+
wildcard.nullTagBitsFromErasedObjectBound = objectBoundNullTagBits;
229224
wildcard.setTypeAnnotations(annotations, this.isAnnotationBasedNullAnalysisEnabled);
230225
return (WildcardBinding) cacheDerivedType(useDerivedTypesOfBound ? bound : genericType, nakedType, wildcard);
231226
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public CaptureBinding(WildcardBinding wildcard, ReferenceBinding sourceType, int
6565
// propagate from wildcard to capture - use super version, because our own method propagates type annotations in the opposite direction:
6666
super.setTypeAnnotations(wildcard.getTypeAnnotations(), wildcard.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled);
6767
this.tagBits |= wildcard.tagBits & (TagBits.HasNullTypeAnnotation|TagBits.HasMissingType);
68+
this.tagBits |= wildcard.nullTagBitsFromErasedObjectBound;
6869
} else {
6970
computeId(this.environment);
7071
this.tagBits |= wildcard.tagBits & (TagBits.AnnotationNullMASK|TagBits.HasNullTypeAnnotation|TagBits.HasMissingType);

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

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public class WildcardBinding extends ReferenceBinding implements HotSwappable{
5252
ReferenceBinding[] superInterfaces;
5353
TypeVariableBinding typeVariable; // corresponding variable
5454
LookupEnvironment environment;
55+
long nullTagBitsFromErasedObjectBound = 0; // stores null info from '? extends @NonNull Object'
5556

5657
/**
5758
* When unbound, the bound denotes the corresponding type variable (so as to retrieve its bound lazily)
@@ -161,46 +162,46 @@ public long determineNullBitsFromDeclaration(Scope scope, Wildcard wildcard) {
161162
}
162163
}
163164
}
164-
if (this.bound != null && this.bound.isValidBinding()) {
165-
long boundNullTagBits = this.bound.tagBits & TagBits.AnnotationNullMASK;
166-
if (boundNullTagBits != 0L) {
167-
if (this.boundKind == Wildcard.SUPER) {
168-
if ((boundNullTagBits & TagBits.AnnotationNullable) != 0) {
169-
if (nullTagBits == 0L) {
170-
nullTagBits = TagBits.AnnotationNullable;
171-
} else if (wildcard != null && (nullTagBits & TagBits.AnnotationNonNull) != 0) {
172-
Annotation annotation = wildcard.bound.findAnnotation(boundNullTagBits);
173-
if (annotation == null) { // false alarm, implicit annotation is no conflict, but should be removed:
174-
// may not be reachable, how could we have an implicit @Nullable (not via @NonNullByDefault)?
175-
TypeBinding newBound = this.bound.withoutToplevelNullAnnotation();
176-
this.bound = newBound;
177-
wildcard.bound.resolvedType = newBound;
178-
} else {
179-
scope.problemReporter().contradictoryNullAnnotationsOnBounds(annotation, nullTagBits);
180-
}
165+
long boundNullTagBits = this.bound != null && this.bound.isValidBinding()
166+
? this.bound.tagBits & TagBits.AnnotationNullMASK
167+
: this.nullTagBitsFromErasedObjectBound;
168+
if (boundNullTagBits != 0L) {
169+
if (this.boundKind == Wildcard.SUPER) {
170+
if ((boundNullTagBits & TagBits.AnnotationNullable) != 0) {
171+
if (nullTagBits == 0L) {
172+
nullTagBits = TagBits.AnnotationNullable;
173+
} else if (wildcard != null && (nullTagBits & TagBits.AnnotationNonNull) != 0) {
174+
Annotation annotation = wildcard.bound.findAnnotation(boundNullTagBits);
175+
if (annotation == null) { // false alarm, implicit annotation is no conflict, but should be removed:
176+
// may not be reachable, how could we have an implicit @Nullable (not via @NonNullByDefault)?
177+
TypeBinding newBound = this.bound.withoutToplevelNullAnnotation();
178+
this.bound = newBound;
179+
wildcard.bound.resolvedType = newBound;
180+
} else {
181+
scope.problemReporter().contradictoryNullAnnotationsOnBounds(annotation, nullTagBits);
181182
}
182183
}
183-
} else {
184-
if ((boundNullTagBits & TagBits.AnnotationNonNull) != 0) {
185-
if (nullTagBits == 0L) {
186-
nullTagBits = TagBits.AnnotationNonNull;
187-
} else if (wildcard != null && (nullTagBits & TagBits.AnnotationNullable) != 0) {
188-
Annotation annotation = wildcard.bound.findAnnotation(boundNullTagBits);
189-
if (annotation == null) { // false alarm, implicit annotation is no conflict, but should be removed:
190-
TypeBinding newBound = this.bound.withoutToplevelNullAnnotation();
191-
this.bound = newBound;
192-
wildcard.bound.resolvedType = newBound;
193-
} else {
194-
scope.problemReporter().contradictoryNullAnnotationsOnBounds(annotation, nullTagBits);
195-
}
184+
}
185+
} else {
186+
if ((boundNullTagBits & TagBits.AnnotationNonNull) != 0) {
187+
if (nullTagBits == 0L) {
188+
nullTagBits = TagBits.AnnotationNonNull;
189+
} else if (wildcard != null && (nullTagBits & TagBits.AnnotationNullable) != 0) {
190+
Annotation annotation = wildcard.bound.findAnnotation(boundNullTagBits);
191+
if (annotation == null) { // false alarm, implicit annotation is no conflict, but should be removed:
192+
TypeBinding newBound = this.bound.withoutToplevelNullAnnotation();
193+
this.bound = newBound;
194+
wildcard.bound.resolvedType = newBound;
195+
} else {
196+
scope.problemReporter().contradictoryNullAnnotationsOnBounds(annotation, nullTagBits);
196197
}
197198
}
198-
if (nullTagBits == 0L && this.otherBounds != null) {
199-
for (TypeBinding otherBound : this.otherBounds) {
200-
if ((otherBound.tagBits & TagBits.AnnotationNonNull) != 0) { // can this happen?
201-
nullTagBits = TagBits.AnnotationNonNull;
202-
break;
203-
}
199+
}
200+
if (nullTagBits == 0L && this.otherBounds != null) {
201+
for (TypeBinding otherBound : this.otherBounds) {
202+
if ((otherBound.tagBits & TagBits.AnnotationNonNull) != 0) { // can this happen?
203+
nullTagBits = TagBits.AnnotationNonNull;
204+
break;
204205
}
205206
}
206207
}
@@ -300,7 +301,9 @@ public char[] constantPoolName() {
300301

301302
@Override
302303
public TypeBinding clone(TypeBinding immaterial) {
303-
return new WildcardBinding(this.genericType, this.rank, this.bound, this.otherBounds, this.boundKind, this.environment);
304+
WildcardBinding clone = new WildcardBinding(this.genericType, this.rank, this.bound, this.otherBounds, this.boundKind, this.environment);
305+
clone.nullTagBitsFromErasedObjectBound = this.nullTagBitsFromErasedObjectBound;
306+
return clone;
304307
}
305308

306309
@Override
@@ -881,4 +884,10 @@ TypeBinding propagateNonConflictingNullAnnotations(TypeBinding type) {
881884
return type;
882885
return this.environment.createAnnotatedType(type, annots);
883886
}
887+
888+
public boolean hasNullTagBits(long nullTagBits) {
889+
if (nullTagBits == this.nullTagBitsFromErasedObjectBound)
890+
return true;
891+
return (this.tagBits & TagBits.AnnotationNullMASK) == nullTagBits;
892+
}
884893
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4173,6 +4173,33 @@ public void testWildcardCapture3() {
41734173
"Unsafe interpretation of method return type as \'@NonNull\' based on the receiver type \'List<@NonNull P>\'. Type \'List<E>\' doesn\'t seem to be designed with null type annotations in mind\n" +
41744174
"----------\n");
41754175
}
4176+
public void testNonNullBoundedWildcard() {
4177+
runNegativeTestWithLibs(new String[] {
4178+
"X.java",
4179+
"""
4180+
import org.eclipse.jdt.annotation.NonNull;
4181+
interface MyList<T> {
4182+
T get(int i);
4183+
}
4184+
public class X {
4185+
@NonNull Object first(MyList<?> list1, MyList<? extends @NonNull Object> list2, boolean flag) {
4186+
if (flag)
4187+
return list1.get(1); // error
4188+
return list2.get(0); // ok
4189+
}
4190+
}
4191+
"""
4192+
},
4193+
getCompilerOptions(),
4194+
"""
4195+
----------
4196+
1. ERROR in X.java (at line 8)
4197+
return list1.get(1); // error
4198+
^^^^^^^^^^^^
4199+
Null type safety: required '@NonNull' but this expression has type 'capture#1-of ?', a free type variable that may represent a '@Nullable' type
4200+
----------
4201+
""");
4202+
}
41764203
public void testLocalArrays() {
41774204
runNegativeTestWithLibs(
41784205
new String[] {

0 commit comments

Comments
 (0)