Skip to content

Commit 1823e66

Browse files
authored
Wildcards are only not equal if they are provably distinct
1 parent dd74b9b commit 1823e66

File tree

3 files changed

+81
-26
lines changed

3 files changed

+81
-26
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import org.checkerframework.checker.tainting.qual.Untainted;
2+
3+
public class TaintingIssue6025 {
4+
5+
public interface A<T> {}
6+
7+
private static final class B<T> {}
8+
9+
public interface C<T1 extends A<?>, T2 extends A<?>> {}
10+
11+
private final B<C<? extends A<?>, ? extends A<?>>> one = new B<>();
12+
private final B<C<? extends A<?>, ? extends A<?>>> two = new B<>();
13+
14+
void f(boolean b) {
15+
// :: error: (assignment)
16+
B<C<@Untainted ?, ?>> three1 = one;
17+
// :: error: (assignment)
18+
B<C<?, @Untainted ?>> three = b ? two : one;
19+
}
20+
}

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

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -206,37 +206,56 @@ public Boolean visitDeclared_Declared(
206206
if (!arePrimeAnnosEqual(type1, type2)) {
207207
return false;
208208
}
209-
210209
// Prevent infinite recursion e.g. in Issue1587b
211210
visitHistory.put(type1, type2, currentTop, true);
212211

213-
boolean result = visitTypeArgs(type1, type2);
214-
visitHistory.put(type1, type2, currentTop, result);
215-
return result;
216-
}
217-
218-
/**
219-
* A helper class for visitDeclared_Declared. There are subtypes of DefaultTypeHierarchy that need
220-
* to customize the handling of type arguments. This method provides a convenient extension point.
221-
*/
222-
protected boolean visitTypeArgs(AnnotatedDeclaredType type1, AnnotatedDeclaredType type2) {
223-
224-
// TODO: ANYTHING WITH RAW TYPES? SHOULD WE HANDLE THEM LIKE DefaultTypeHierarchy, i.e. use
225-
// ignoreRawTypes
226-
List<? extends AnnotatedTypeMirror> type1Args = type1.getTypeArguments();
227-
List<? extends AnnotatedTypeMirror> type2Args = type2.getTypeArguments();
228-
229-
if (type1Args.isEmpty() && type2Args.isEmpty()) {
230-
return true;
212+
List<AnnotatedTypeMirror> type1Args = type1.getTypeArguments();
213+
List<AnnotatedTypeMirror> type2Args = type2.getTypeArguments();
214+
215+
// Capture the types because the wildcards are only not equal if they are provably distinct.
216+
// Provably distinct is computed using the captured and erased upper bounds of wildcards.
217+
// See JLS 4.5.1. Type Arguments of Parameterized Types.
218+
AnnotatedTypeFactory atypeFactory = type1.atypeFactory;
219+
AnnotatedDeclaredType capturedType1 =
220+
(AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type1);
221+
AnnotatedDeclaredType capturedType2 =
222+
(AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type2);
223+
visitHistory.put(capturedType1, capturedType2, currentTop, true);
224+
225+
List<AnnotatedTypeMirror> capturedType1Args = capturedType1.getTypeArguments();
226+
List<AnnotatedTypeMirror> capturedType2Args = capturedType2.getTypeArguments();
227+
boolean result = true;
228+
for (int i = 0; i < type1.getTypeArguments().size(); i++) {
229+
AnnotatedTypeMirror type1Arg = type1Args.get(i);
230+
AnnotatedTypeMirror type2Arg = type2Args.get(i);
231+
Boolean pastResultTA = visitHistory.get(type1Arg, type2Arg, currentTop);
232+
if (pastResultTA != null) {
233+
result = pastResultTA;
234+
} else {
235+
if (type1Arg.getKind() != TypeKind.WILDCARD || type2Arg.getKind() != TypeKind.WILDCARD) {
236+
result = areEqual(type1Arg, type2Arg);
237+
} else {
238+
AnnotatedWildcardType wildcardType1 = (AnnotatedWildcardType) type1Arg;
239+
AnnotatedWildcardType wildcardType2 = (AnnotatedWildcardType) type2Arg;
240+
if (type1.atypeFactory.ignoreUninferredTypeArguments
241+
&& (wildcardType1.isUninferredTypeArgument()
242+
|| wildcardType2.isUninferredTypeArgument())) {
243+
result = true;
244+
} else {
245+
AnnotatedTypeMirror capturedType1Arg = capturedType1Args.get(i);
246+
AnnotatedTypeMirror capturedType2Arg = capturedType2Args.get(i);
247+
result = areEqual(capturedType1Arg.getErased(), capturedType2Arg.getErased());
248+
}
249+
}
250+
}
251+
if (!result) {
252+
break;
253+
}
231254
}
232255

233-
if (type1Args.size() == type2Args.size()) {
234-
return areAllEqual(type1Args, type2Args);
235-
} else {
236-
throw new BugInCF(
237-
"Mismatching type argument sizes:%n type 1: %s (%d)%n type 2: %s (%d)",
238-
type1, type1Args.size(), type2, type2Args.size());
239-
}
256+
visitHistory.put(capturedType1, capturedType2, currentTop, result);
257+
visitHistory.put(type1, type2, currentTop, result);
258+
return result;
240259
}
241260

242261
/**
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
public class Issue6025 {
2+
3+
public interface A<T> {}
4+
5+
private static final class B<T> {}
6+
7+
public interface C<T1 extends A<?>, T2 extends A<?>> {}
8+
9+
private final B<C<? extends A<?>, ? extends A<?>>> one = new B<>();
10+
private final B<C<? extends A<?>, ? extends A<?>>> two = new B<>();
11+
12+
void f(boolean b) {
13+
B<C<?, ?>> three1 = one;
14+
B<C<?, ?>> three = b ? two : one;
15+
}
16+
}

0 commit comments

Comments
 (0)