javachecker-frameworkerrorprone

Using ErrorProne to enforce a type annotation?


Is there an example for using ErrorProne to enforce an annotation on types and parameters?

For example,

@EventKey private static final String VALID_KEY = "asdf";

Map<@EventKey String, Object> map = new HashMap<>();

public void addSomeValues() {
    map.put("invalid_key", new Object()); // should error
    map.put(VALID_KEY, new Object()); // should pass
}

public void put(@EventKey String key, Object value) {
    map.put(key, value);
}

public void usingCustomPut(){

    put("invalid_key", new Object()); // should error
    put(VALID_KEY, new Object()); // should pass
}

Solution

  • Error Prone has a hard-coded set of checks. You could extend Error Prone, but this would require forking it, editing the source code, rebuilding it, and using your own custom version.

    The Checker Framework is a pluggable type-checker designed for checking type annotations. It serves your purpose.

    Suppose that you defined the @EventKey annotation as shown below. (This is following the instructions for creating a new checker.)

    Then you can run the command

    java -jar "${CHECKERFRAMEWORK}/checker/dist/checker.jar" -cp . \
    -processor org.checkerframework.common.subtyping.SubtypingChecker \
    -Aquals=UnknownEventKey,EventKey TestCase.java
    

    and it behaves exactly as you wish, with the correct lines permitted and the incorrect lines forbidden (by compiler error messages).

    (One caveat: The Checker Framework also issues a warning for the assignment VALID_KEY = "asdf", because there is no way for it to know that that assignment is legal. You can suppress that warning if you are sure it is legal.)

    Here are the annotation definitions, for completeness:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Target;
    import org.checkerframework.framework.qual.DefaultQualifierInHierarchy;
    import org.checkerframework.framework.qual.SubtypeOf;
    
    /** The value might or might not be an Event Key. */
    @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
    @SubtypeOf({})
    @DefaultQualifierInHierarchy
    public @interface UnknownEventKey {}
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Target;
    import org.checkerframework.framework.qual.ImplicitFor;
    import org.checkerframework.framework.qual.LiteralKind;
    import org.checkerframework.framework.qual.SubtypeOf;
    
    /** The value is an Event Key. */
    @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
    @SubtypeOf({UnknownEventKey.class})
    @ImplicitFor(literals = LiteralKind.NULL)
    public @interface EventKey {}