Search code examples
javaeclipseannotationsnullableeclipse-jdt

Annotation-based null analysis - warning appears only with array parameter


I am getting the following (confusing) warning when using annotation-based null analysis where an array is involved:

Null type safety (type annotations): The expression of type 'int[]' needs unchecked conversion to conform to 'int @Nullable[]'

This occurs when I pass an unannotated int[] to an int @Nullable[] parameter.

This is surprising. Normally it is only a problem if you take a nullable value and try to pass it to a nonnull parameter, but I am doing the opposite - taking a known non-null (though not annotated) array and passing it to a method (Arrays.equals) which does accept nulls.

Also it does not seem to be a problem with non-array objects. Normally a variable or return of (unannotated, non-array) type T can be assigned to a @Nullable T.

So my question is why does this change when T is an array type?


My class is a hashable/equality-comparable proxy for a native class that uses an int[] (taken from a C++ function) as a unique identifier. My package uses annotation-based null analysis and the 3rd-party package (with the native class) does not.

Here's a cut-down version that shows the problem:

TagKey.java:

package example;

import java.util.Arrays;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import other.Tag;

import com.google.common.collect.Interner;
import com.google.common.collect.Interners;

public class TagKey {

    private TagKey(Tag tag) {
        this.tag = tag;
    }

    public static TagKey forTag(Tag tag) {
        TagKey candidateKey = new TagKey(tag);
        return INTERNER.intern(candidateKey);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(this.tag.getUniqueIdentifier());
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (this == obj) {
            return true;
        } else if (obj == null) {
            return false;
        } else if (getClass() != obj.getClass()) {
            return false;
        } else {
            return ((TagKey) obj).hasMatchingIdentifier(this.tag.getUniqueIdentifier()); // Warning appears here
        }
    }

    public boolean hasMatchingIdentifier(int @Nullable[] id) {
        return Arrays.equals(this.tag.getUniqueIdentifier(), id);
    }

    @SuppressWarnings("null") // Guava cannot use type parameter annotations due to backward compatibility with Java 6
    private static final Interner<TagKey> INTERNER = Interners.newWeakInterner();

    private final Tag tag;

}

package-info.java:

/**
 * @author finnw
 *
 */
@org.eclipse.jdt.annotation.NonNullByDefault
package example;

Tag.java: (partial)

package other;

public class Tag {
    public native int[] getUniqueIdentifier(); // Currently returns a fresh array on each call, but may change in the near future to reuse a single array
}

The workaround I am currently using:

    public boolean hasMatchingIdentifier(@Nullable Object id) {
        return id instanceof int[] &&
               Arrays.equals(this.tag.getUniqueIdentifier(), (int[])id);
    }

I would prefer to avoid these approaches:

  • Adding @SuppressWarnings("null") to TagKey or its equals method (The production version is somewhat more complex and has a high risk of embarrassing NPEs.)

Notes:

  • My JDT version is 3.10.0.v20140606-1215
  • Earlier I made the mistake of declaring @Nullable int[] id. Surprisingly there was no warning message for this, even though it implies that the primitive int elements may be null which is clearly wrong.

Solution

  • Add this method:

    @SuppressWarnings("null")
    public static int @Nullable[] getUniqueIdentifier(Tag tag) {
        return tag.getUniqueIdentifier();
    }
    

    then:

    return ((TagKey) obj).hasMatchingIdentifier(getUniqueIdentifier(this.tag));
    

    This is why I'm ignoring the "unchecked conversion from non-annotated" warning until nullity profiles are supported, you get stuck either suppressing null warnings everywhere (which defeats the point) or making annotated wrapper methods for every library.