Skip to content

Commit 9638af0

Browse files
authored
Merge branch 'master' into poly-typeargs
2 parents 6656f9a + e558998 commit 9638af0

File tree

13 files changed

+398
-25
lines changed

13 files changed

+398
-25
lines changed

checker/src/main/java/org/checkerframework/checker/index/substringindex/SubstringIndexAnnotatedTypeFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ protected DependentTypesHelper createDependentTypesHelper() {
8080

8181
/**
8282
* The Substring Index qualifier hierarchy. The hierarchy consists of a top element {@link
83-
* UNKNOWN} of type {@link SubstringIndexUnknown}, bottom element {@link BOTTOM} of type {@link
84-
* SubstringIndexBottom}, and elements of type {@link SubstringIndexFor} that follow the
83+
* #UNKNOWN} of type {@link SubstringIndexUnknown}, bottom element {@link #BOTTOM} of type
84+
* {@link SubstringIndexBottom}, and elements of type {@link SubstringIndexFor} that follow the
8585
* subtyping relation of {@link UBQualifier}.
8686
*/
8787
private final class SubstringIndexQualifierHierarchy extends ElementQualifierHierarchy {
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import org.checkerframework.checker.nullness.qual.NonNull;
2+
import org.checkerframework.checker.nullness.qual.Nullable;
3+
4+
class WildcardBounds {
5+
6+
abstract class OuterNbl<T extends @Nullable Object> {
7+
abstract T get();
8+
9+
abstract class Inner<U extends T> {
10+
abstract U get();
11+
12+
abstract class Chain<V extends U, W extends V> {
13+
abstract W get();
14+
}
15+
16+
Object m0(Chain<? extends Object, ? extends Object> p) {
17+
return p.get();
18+
}
19+
20+
Object m1(Chain<? extends @NonNull T, ? extends @NonNull T> p) {
21+
return p.get();
22+
}
23+
24+
Object m2(Chain<?, ?> p) {
25+
// :: error: (return.type.incompatible)
26+
return p.get();
27+
}
28+
29+
Object m3(Chain<? extends @Nullable Object, ? extends @Nullable Object> p) {
30+
// :: error: (return.type.incompatible)
31+
return p.get();
32+
}
33+
34+
Object m4(Chain<@NonNull ?, @NonNull ?> p) {
35+
return p.get();
36+
}
37+
38+
void callsNonNull(
39+
OuterNbl<Object>.Inner<Number> i,
40+
OuterNbl<Object>.Inner<Number>.Chain<Integer, Integer> n) {
41+
i.m0(n);
42+
i.m1(n);
43+
i.m2(n);
44+
i.m3(n);
45+
i.m4(n);
46+
}
47+
48+
void callsNullable(
49+
OuterNbl<@Nullable Object>.Inner<@Nullable Number> i,
50+
OuterNbl<@Nullable Object>.Inner<@Nullable Number>.Chain<
51+
@Nullable Integer, @Nullable Integer>
52+
n) {
53+
// :: error: (argument.type.incompatible)
54+
i.m0(n);
55+
// :: error: (argument.type.incompatible)
56+
i.m1(n);
57+
// OK
58+
i.m2(n);
59+
// OK
60+
i.m3(n);
61+
// :: error: (argument.type.incompatible)
62+
i.m4(n);
63+
}
64+
}
65+
66+
Object m0(Inner<? extends Object> p) {
67+
return p.get();
68+
}
69+
70+
Object m1(Inner<? extends @NonNull T> p) {
71+
return p.get();
72+
}
73+
74+
Object m2(Inner<?> p) {
75+
// :: error: (return.type.incompatible)
76+
return p.get();
77+
}
78+
79+
Object m3(Inner<? extends @Nullable Object> p) {
80+
// :: error: (return.type.incompatible)
81+
return p.get();
82+
}
83+
84+
Object m4(Inner<@NonNull ?> p) {
85+
return p.get();
86+
}
87+
88+
// We could add calls for these methods.
89+
}
90+
91+
Object m0(OuterNbl<? extends Object> p) {
92+
return p.get();
93+
}
94+
95+
Object m1(OuterNbl<? extends @NonNull Object> p) {
96+
return p.get();
97+
}
98+
99+
Object m2(OuterNbl<?> p) {
100+
// :: error: (return.type.incompatible)
101+
return p.get();
102+
}
103+
104+
Object m3(OuterNbl<? extends @Nullable Object> p) {
105+
// :: error: (return.type.incompatible)
106+
return p.get();
107+
}
108+
109+
Object m4(OuterNbl<@NonNull ?> p) {
110+
return p.get();
111+
}
112+
113+
void callsOuter(OuterNbl<String> s, OuterNbl<@Nullable String> ns) {
114+
m0(s);
115+
m1(s);
116+
m2(s);
117+
m3(s);
118+
m4(s);
119+
120+
// :: error: (argument.type.incompatible)
121+
m0(ns);
122+
// :: error: (argument.type.incompatible)
123+
m1(ns);
124+
// OK
125+
m2(ns);
126+
// OK
127+
m3(ns);
128+
// :: error: (argument.type.incompatible)
129+
m4(ns);
130+
}
131+
132+
// We could add an OuterNonNull to also test with a non-null upper bound.
133+
// But we probably already test that enough.
134+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import org.checkerframework.checker.nullness.qual.NonNull;
2+
import org.checkerframework.checker.nullness.qual.Nullable;
3+
4+
class WildcardOverrideMore {
5+
interface Box<X extends @Nullable Object> {}
6+
7+
interface Super<T extends @Nullable Object> {
8+
<U extends T> void foo(Box<? extends U> lib);
9+
10+
<U extends T> Box<? extends U> retfoo();
11+
12+
<U extends T> void bar(Box<? super U> lib);
13+
14+
<U extends T> Box<? super U> retbar();
15+
}
16+
17+
interface Sub<V extends @Nullable Object> extends Super<V> {
18+
@Override
19+
<W extends V> void foo(Box<? extends W> lib);
20+
21+
@Override
22+
<W extends V> Box<? extends W> retfoo();
23+
24+
@Override
25+
<W extends V> void bar(Box<? super W> lib);
26+
27+
@Override
28+
<W extends V> Box<? super W> retbar();
29+
}
30+
31+
interface SubErrors<V extends @Nullable Object> extends Super<V> {
32+
@Override
33+
// :: error: (override.param.invalid)
34+
<W extends V> void foo(Box<? extends @NonNull W> lib);
35+
36+
@Override
37+
// :: error: (override.return.invalid)
38+
<W extends V> Box<? extends @Nullable W> retfoo();
39+
40+
@Override
41+
// :: error: (override.param.invalid)
42+
<W extends V> void bar(Box<@NonNull ? super @NonNull W> lib);
43+
44+
@Override
45+
// :: error: (override.return.invalid)
46+
<W extends V> Box<@Nullable ? super @NonNull W> retbar();
47+
}
48+
}

docs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ and a redundant warning if they are annotated with `@NonNull`.
88

99
**Implementation details:**
1010

11+
Fixed intersection of wildcards with extends bounds, to ensure the correct bounds are used.
12+
1113
**Closed issues:**
1214

1315
eisop#1003, eisop#1033, eisop#1058.

framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public int hashCode() {
236236
/**
237237
* Returns a representation of this diagnostic as if it appeared in a diagnostics file. This
238238
* uses only the base file name, not the full path, and only the message key, not the full
239-
* message. Field {@link messageKeyParens} influences whether the message key is output in
239+
* message. Field {@link #messageKeyParens} influences whether the message key is output in
240240
* parentheses.
241241
*
242242
* @return a representation of this diagnostic as if it appeared in a diagnostics file

framework/src/main/java/org/checkerframework/common/basetype/BaseTypeValidator.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,13 @@ protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedT
628628
if (!atypeFactory
629629
.getTypeHierarchy()
630630
.isSubtype(captureTypeVarUB, wildcard.getExtendsBound())) {
631+
// For most captured type variables, this will trivially hold, as capturing
632+
// incorporated the extends bound of the wildcard into the upper bound of the
633+
// type variable.
634+
// This will fail if the bound and the wildcard have generic types and there is
635+
// no appropriate GLB.
636+
// This issues an error for types that cannot be satisfied, because the two
637+
// bounds have contradictory requirements.
631638
checker.reportError(
632639
tree.getTypeArguments().get(i),
633640
"type.argument.type.incompatible",

framework/src/main/java/org/checkerframework/common/returnsreceiver/FluentAPIGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private enum FluentAPIGenerators {
5151
/**
5252
* The qualified name of the AutoValue Builder annotation. This needs to be constructed
5353
* dynamically due to a side effect of the shadow plugin. See {@link
54-
* getAutoValueBuilderCanonicalName()} for more information.
54+
* #getAutoValueBuilderCanonicalName()} for more information.
5555
*/
5656
private final String AUTO_VALUE_BUILDER = getAutoValueBuilderCanonicalName();
5757

framework/src/main/java/org/checkerframework/framework/stub/AnnotationFileParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2070,7 +2070,7 @@ private void annotateTypeParameters(
20702070
* zero or more mappings. Each mapping is from an element that {@code member} would override to
20712071
* {@code member}.
20722072
*
2073-
* <p>This method does not read or write field {@link annotationFileAnnos}.
2073+
* <p>This method does not read or write field {@link #annotationFileAnnos}.
20742074
*
20752075
* @param elementsToDecl the mapping that is side-effected by this method
20762076
* @param fakeOverrideDecls fake overrides, also side-effected by this method

framework/src/main/java/org/checkerframework/framework/type/DefaultTypeHierarchy.java

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public class DefaultTypeHierarchy extends AbstractAtmComboVisitor<Boolean, Void>
8282
protected final StructuralEqualityVisitHistory areEqualVisitHistory;
8383

8484
/** The Covariant.value field/element. */
85-
final ExecutableElement covariantValueElement;
85+
protected final ExecutableElement covariantValueElement;
8686

8787
/**
8888
* Creates a DefaultTypeHierarchy.
@@ -392,8 +392,8 @@ protected boolean isContainedBy(
392392
areEqualVisitHistory.put(inside, outside, currentTop, result);
393393
return result;
394394
}
395-
if ((TypesUtils.isCapturedTypeVariable(outside.getUnderlyingType())
396-
&& !TypesUtils.isCapturedTypeVariable(inside.getUnderlyingType()))) {
395+
if (TypesUtils.isCapturedTypeVariable(outside.getUnderlyingType())
396+
&& !TypesUtils.isCapturedTypeVariable(inside.getUnderlyingType())) {
397397
// TODO: This branch should be removed after #979 is fixed.
398398
// This workaround is only needed when outside is a captured type variable,
399399
// but inside is not.
@@ -977,33 +977,58 @@ public Boolean visitTypevar_Typevar(
977977
TypeMirror superTM = supertype.getUnderlyingType();
978978
if (AnnotatedTypes.haveSameDeclaration(checker.getTypeUtils(), subtype, supertype)) {
979979
// The underlying types of subtype and supertype are uses of the same type parameter,
980-
// but they
981-
// may have different primary annotations.
982-
boolean subtypeHasAnno = subtype.getAnnotationInHierarchy(currentTop) != null;
983-
boolean supertypeHasAnno = supertype.getAnnotationInHierarchy(currentTop) != null;
980+
// but they may have different primary annotations.
981+
AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop);
982+
boolean subtypeHasAnno = subtypeAnno != null;
983+
AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop);
984+
boolean supertypeHasAnno = supertypeAnno != null;
984985

985986
if (subtypeHasAnno && supertypeHasAnno) {
986987
// If both have primary annotations then just check the primary annotations
987988
// as the bounds are the same.
988989
return isPrimarySubtype(subtype, supertype);
989990
} else if (!subtypeHasAnno && !supertypeHasAnno) {
990-
// two unannotated uses of the same type parameter are of the same type
991-
return areEqualInHierarchy(subtype, supertype);
991+
// Two unannotated uses of the same type parameter need to compare
992+
// both upper and lower bounds.
993+
994+
// Upper bound of the subtype needs to be below the upper bound of the supertype.
995+
if (!qualHierarchy.isSubtypeShallow(
996+
subtype.getEffectiveAnnotationInHierarchy(currentTop),
997+
subTM,
998+
supertype.getEffectiveAnnotationInHierarchy(currentTop),
999+
superTM)) {
1000+
return false;
1001+
}
1002+
1003+
// Lower bound of the subtype needs to be below the lower bound of the supertype.
1004+
// TODO: Think through this and add better test coverage.
1005+
AnnotationMirrorSet subLBs =
1006+
AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, subtype);
1007+
AnnotationMirror subLB =
1008+
qualHierarchy.findAnnotationInHierarchy(subLBs, currentTop);
1009+
AnnotationMirrorSet superLBs =
1010+
AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, supertype);
1011+
AnnotationMirror superLB =
1012+
qualHierarchy.findAnnotationInHierarchy(superLBs, currentTop);
1013+
return qualHierarchy.isSubtypeShallow(subLB, subTM, superLB, superTM);
9921014
} else if (subtypeHasAnno && !supertypeHasAnno) {
9931015
// This is the case "@A T <: T" where T is a type variable.
1016+
// TODO: should this also test the upper bounds?
9941017
AnnotationMirrorSet superLBs =
9951018
AnnotatedTypes.findEffectiveLowerBoundAnnotations(qualHierarchy, supertype);
9961019
AnnotationMirror superLB =
9971020
qualHierarchy.findAnnotationInHierarchy(superLBs, currentTop);
998-
return qualHierarchy.isSubtypeShallow(
999-
subtype.getAnnotationInHierarchy(currentTop), subTM, superLB, superTM);
1021+
return qualHierarchy.isSubtypeShallow(subtypeAnno, subTM, superLB, superTM);
10001022
} else if (!subtypeHasAnno && supertypeHasAnno) {
10011023
// This is the case "T <: @A T" where T is a type variable.
1024+
// TODO: should this also test the lower bounds?
10021025
return qualHierarchy.isSubtypeShallow(
10031026
subtype.getEffectiveAnnotationInHierarchy(currentTop),
10041027
subTM,
1005-
supertype.getAnnotationInHierarchy(currentTop),
1028+
supertypeAnno,
10061029
superTM);
1030+
} else {
1031+
throw new BugInCF("Unreachable");
10071032
}
10081033
}
10091034

framework/src/main/java/org/checkerframework/framework/util/AnnotatedTypes.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -955,13 +955,17 @@ private static AnnotatedTypeMirror glbSubtype(
955955
if (subtype.getKind() != TypeKind.TYPEVAR) {
956956
throw new BugInCF("Missing primary annotations: subtype: %s", subtype);
957957
}
958-
AnnotationMirrorSet lb = findEffectiveLowerBoundAnnotations(qualHierarchy, subtype);
959-
AnnotationMirror lbAnno = qualHierarchy.findAnnotationInHierarchy(lb, top);
960-
if (lbAnno != null
961-
&& !qualHierarchy.isSubtypeShallow(lbAnno, subTM, superAnno, superTM)) {
962-
// The superAnno is lower than the lower bound annotation, so add it.
963-
glb.addAnnotation(superAnno);
964-
} // else don't add any annotation.
958+
AnnotationMirror ubAnno = subtype.getEffectiveAnnotationInHierarchy(top);
959+
if (!qualHierarchy.isSubtypeQualifiersOnly(ubAnno, superAnno)) {
960+
// Instead of superAnno <: ubAnno check for ubAnno <!: superAnno to exclude the
961+
// case where ubAnno == superAnno.
962+
// We know that `glb` is a type variable, because `subtype` is.
963+
// Do not add the annotation to the type variable itself, because that would
964+
// change the upper and the lower bound.
965+
// Adding the more restrictive `superAnno` only to the upper bound ensures that
966+
// the type variable is below `superAnno`.
967+
((AnnotatedTypeVariable) glb).getUpperBound().replaceAnnotation(superAnno);
968+
}
965969
} else {
966970
throw new BugInCF("GLB: subtype: %s, supertype: %s", subtype, supertype);
967971
}

framework/src/main/java/org/checkerframework/framework/util/QualifierKindHierarchy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
* ElementQualifierHierarchy} (but <em>not</em> {@link MostlyNoElementQualifierHierarchy}) to
3131
* implement methods that compare {@link javax.lang.model.element.AnnotationMirror}s, such as {@link
3232
* org.checkerframework.framework.type.QualifierHierarchy#isSubtypeShallow(AnnotationMirror,
33-
* TypeMirror, AnnotationMirror, TypeMirror)}.
33+
* javax.lang.model.type.TypeMirror, AnnotationMirror, javax.lang.model.type.TypeMirror)}.
3434
*
3535
* @see DefaultQualifierKindHierarchy
3636
* @see org.checkerframework.framework.util.DefaultQualifierKindHierarchy.DefaultQualifierKind
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Minimized test case from
2+
//
3+
// https://github.com/eisop/guava/blob/290cfe5c926de28cfdda491535901b09bab90ef9/guava/src/com/google/common/reflect/TypeToken.java#L1228
4+
// which failed in https://github.com/eisop/checker-framework/pull/1066 with:
5+
//
6+
// guava/guava/src/com/google/common/reflect/TypeToken.java:[1228,29] error: [[value,
7+
// allcheckers]:return.type.incompatible] incompatible types in return.
8+
// type of expression: @UnknownVal TypeToken<capture#07[ extends capture#08[ extends T[ extends
9+
// @UnknownVal Object super @UnknownVal Void] super @BottomVal Void] super @BottomVal Void]>
10+
// method return type: @UnknownVal TypeToken<?[ extends T[ extends @UnknownVal Object super
11+
// @BottomVal Void] super @BottomVal Void]>
12+
13+
abstract class WildcardInReturn<T> {
14+
15+
abstract WildcardInReturn<?> of(String key);
16+
17+
abstract WildcardInReturn<? extends T> getSubtype(Class<?> subclass);
18+
19+
WildcardInReturn<? extends T> getSubtypeFromLowerBounds(Class<?> subclass, String key) {
20+
@SuppressWarnings("unchecked") // T's lower bound is <? extends T>
21+
WildcardInReturn<? extends T> bound = (WildcardInReturn<? extends T>) of(key);
22+
// Java supports only one lowerbound anyway.
23+
return bound.getSubtype(subclass);
24+
}
25+
}

0 commit comments

Comments
 (0)