spacether
spacether

Reputation: 2699

Using the java checker framework, why is a NonNull value not accepted into a Nullable value location?

Using the java checker framework, why is a Map NonNull value not accepted into a Map Nullable value location?

With this code invocation:

        schema.validate(
            MapMaker.makeMap(
                new AbstractMap.SimpleEntry<>(
                    "foo",
                    "baz"
                ),
                new AbstractMap.SimpleEntry<>(
                    "bar",
                    2
                )
            ),
            configuration
        );

I get this error:

java: [argument] incompatible argument for parameter arg of validate.
  found   : @Initialized @NonNull Map<@Initialized @NonNull String, @Initialized @NonNull Object>
  required: @Initialized @NonNull Map<@Initialized @NonNull String, @Initialized @Nullable Object>

And the validate method is defined as: public FrozenMap<@Nullable Object> validate(Map<String, @Nullable Object> arg, SchemaConfiguration configuration) throws ValidationException, InvalidTypeException {

Any non null Object is a subset of the set of nullable Objects, so why does this not work? How do I get this to work for non-nullable key input also? Do I need to have a different input method signature for the nullable and the non-nullable input arg?

My arguments passed in to the validate method will be used for reading only. Per the checker framework javadocs, @Covariant may be a solution here: For example, consider Iterator. A client can read elements but not write them, so Iterator<@Nullable String> can be a subtype of Iterator<String> without introducing a hole in the type system. Therefore, its type parameter is annotated with @Covariant. The first type parameter of Map.Entry is also covariant. Another example would be the type parameter of a hypothetical class ImmutableList.

But that only applies to interfaces.

Upvotes: 1

Views: 220

Answers (1)

spacether
spacether

Reputation: 2699

So this is happening because the checker framework sees

  • String
  • @Nullable String as two different classes, where String (@NonNull String) inherits from @Nullable string Like in normal Java, covariance does not apply.

So the solutions here are to:

  1. use extends to allow covariance like public FrozenMap<@Nullable Object> validate(Map<String, ? extends @Nullable Object> arg, SchemaConfiguration configuration) throws ValidationException, InvalidTypeException {
  2. update the validate method to accept Map<String, ?> This is not great because type checking info is lost from the signature
  3. or write many methods that accept all combinations with/without @Nullable like Map<String, @Nullable Object> arg Map<String, Object> arg
  4. or write an interface that makes a generic parameter for each nullable parameter, and implement it.

Number 4 example code would look like

@Covariant(0)
public interface MapValidator <InType extends @Nullable Object, OutType> {
    OutType validate(Map<String, InType> arg, SchemaConfiguration configuration) throws ValidationException, InvalidTypeException;
}

public class SomeSchema implements MapValidator<@Nullable Object, FrozenMap<@Nullable Object>> {
    OutType validate(Map<String, @Nullable Object> arg, SchemaConfiguration configuration) throws ValidationException, InvalidTypeException {
        ...
    }

}

Upvotes: 0

Related Questions