import java.util.HashMap;
import org.checkerframework.checker.nullness.qual.*;
public class KeyForSubtyping {
HashMap<String, String> mapA = new HashMap<String, String>();
HashMap<String, String> mapB = new HashMap<String, String>();
HashMap<String, String> mapC = new HashMap<String, String>();
public void testSubtypeAssignments(
String not_a_key,
@KeyFor("this.mapA") String a,
@KeyFor("this.mapB") String b,
@KeyFor({"this.mapA", "this.mapB"}) String ab) {
// Try the error cases first, otherwise dataflow will change the inferred
// annotations on the variables such that a line of code can have an
// effect on a subsequent line of code. We want each of these tests to
// be independent.
//:: error: (assignment.type.incompatible)
ab = a;
//:: error: (assignment.type.incompatible)
ab = b;
//:: error: (assignment.type.incompatible)
a = b;
//:: error: (assignment.type.incompatible)
a = not_a_key;
//:: error: (assignment.type.incompatible)
b = not_a_key;
//:: error: (assignment.type.incompatible)
ab = not_a_key;
// Now try the success cases
a = ab;
b = ab;
not_a_key = ab;
not_a_key = a;
}
public void testDataFlow(
String not_yet_a_key,
@KeyFor("this.mapA") String a,
@KeyFor("this.mapB") String b,
@KeyFor({"this.mapA", "this.mapB"}) String ab) {
// Test that when a valid assignment is made, dataflow transfers the
// KeyFor type qualifier from the right hand side to the left hand side.
//:: error: (argument.type.incompatible)
method1(not_yet_a_key);
not_yet_a_key = a;
method1(not_yet_a_key);
method1(a);
//:: error: (argument.type.incompatible)
method1(b);
method1(ab);
b = ab;
method1(b);
}
public void testSetOrdering(
@KeyFor({"this.mapC", "this.mapA"}) String ac,
@KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) {
// Test that the order of elements in the set doesn't matter when doing subtyping checks,
// @KeyFor("A, B, C") <: @KeyFor("C, A")
// Try the error case first - see the note in method testSubtypeAssignments
//:: error: (assignment.type.incompatible)
abc = ac;
ac = abc;
}
public void testDataflowTransitivity(
@KeyFor({"this.mapA"}) String a,
@KeyFor({"this.mapA", "this.mapB"}) String ab,
@KeyFor({"this.mapA", "this.mapB", "this.mapC"}) String abc) {
ab = abc;
// At this point, dataflow should have refined the type of ab to @KeyFor({"this.mapA","this.mapB","this.mapC"})
a = ab;
// At this point, dataflow should have refined the type of a to @KeyFor({"this.mapA","this.mapB","this.mapC"})
// This would not succeed without the previous two assignments, but should now because of dataflow.
abc = a;
}
private void method1(@KeyFor("this.mapA") String a) {}
private void testWithNullnessAnnotation(
String not_a_key,
@KeyFor("this.mapA") String a,
@KeyFor("this.mapB") String b,
@Nullable @KeyFor({"this.mapA", "this.mapB"}) String ab) {
// These fail only because a @Nullable RHS cannot be assigned to a @NonNull LHS.
//:: error: (assignment.type.incompatible)
a = ab;
//:: error: (assignment.type.incompatible)
b = ab;
//:: error: (assignment.type.incompatible)
not_a_key = ab;
not_a_key = a; // Succeeds because both sides are @NonNull
}
// Test overriding
static class Super {
HashMap<String, String> map1 = new HashMap<String, String>();
HashMap<String, String> map2 = new HashMap<String, String>();
void method1(@KeyFor({"this.map1", "this.map2"}) String s) {}
void method2(@KeyFor("this.map1") String s) {}
}
static class Sub extends Super {
@Override
void method1(@KeyFor("this.map1") String s) {}
@Override
//:: error: (override.param.invalid)
void method2(@KeyFor({"this.map1", "this.map2"}) String s) {}
}
}