Piotr-
> Is there a way of telling the checker that '?' can be @Nullable?
This is an interesting problem. It's not a bug, but it is a limitation of
the syntax. I will present a workaround, but for the most part this email
expands on and explains the problem, and asks for feedback in finding the
best solution.
For type of the parameter to cast(), you want to write:
Consumer<? super A extends /*@Nullable*/ Object>
The type argument has both an upper bound ("extends") and a lower bound
("super"). However, Java permits you to specify at most one bound. From a
type-theoretic point of view, it makes sense to specify both: every type
parameter has an upper and a lower bound. (Java fills in any missing
bounds, using the trivial bounds "extends Object" or "super void".)
The Checker Framework's error message printed out both the upper and lower
bound (I have slightly edited for clarity):
> incompatible types.
> consumer.consume(object);
> ^
> found : @Nullable A extends @Nullable Object
> required: ? extends @NonNull Object super @Nullable A
even though you wouldn't be able to paste that in a Java program.
Since Java only lets you write one of the bounds, the other one gets filled
in for you automatically. You wrote, in your original code:
Consumer<? super A>
That is equivalent to:
Consumer<? super A extends Object>
You are using a default qualifier of @NonNull, so it is equivalent to:
Consumer<? super A extends @NonNull Object>
That's not what you want. You need a way to override the @NonNull that was
automatically inserted for you by the Checker Framework's defaulting
mechanism.
Here is code that changes the default locally, and that passes the nullness
type-checker:
---------------- start code ----------------
import checkers.nullness.quals.*;
import checkers.quals.*;
interface Consumer<A extends /*@Nullable*/ Object> {
public void consume(A object);
}
class Utils {
public static <A extends Object>
Consumer<A> cast(@DefaultQualifier("checkers.nullness.quals.Nullable") final /*@NonNull*/ Consumer<? super A> consumer) {
return new Consumer<A>() {
@Override public void consume(A object) {
consumer.consume(object);
}
};
}
}
---------------- end code ----------------
The change of default makes
Consumer<? super A>
equivalent to
Consumer<? super A extends @Nullable Object>
This syntax, with the @Nullable default applied to just one parameter, is
ugly and verbose.
In your example, using a default of @Nullable for the entire Utils class
also works. It is less ugly and, in a larger codebase, might even lead to
fewer annotations overall.
Here are some ways to cope with the problem in the future:
* Continue to use the current defaulting mechanism, which lets users
specify what default will be inserted. Probably, there is rarely a need
to use different defaults for different implicit type bounds in a single
method or class. In other words, the problem may be minor or
non-existent in practice. More experience would be needed to know for
sure.
Incidentally, some people like the idea of per-method, per-class, and
per-package defaults. Other people think that localized defaults will
lead to confusion, because the meaning of the types in a code snippet
cannot be understood without knowledge of the defaults. This question,
too, requires more experimentation and experience.
* Special-case the defaulting rules for implicit "extends" type bounds.
Permit the user to set these defaults independently of the overall
defaults. This may not help much if you have code that intermixes
classes that need different defaults.
This is somewhat similar to the fact that local variables can be
defaulted differently than other locations.
(I would prefer to avoid use of too many fine-grained defaulting
mechanisms. I fear that giving users fine-grained control over
defaulting will lead to confusion.)
* Introduce a way to write annotations on implicitly added type bounds.
The difficulty is in finding a syntactic location, since Java provides
no such location. One possibility is before the wildcard. Under this
syntax proposal,
Consumer<@Nullable ? super A>
would mean
Consumer<? super A extends @Nullable Object>
and
List<@Nullable ? super @NonNull Number>
would mean
List<? super @NonNull Number extends @Nullable Object>
For code that intermixes classes that need different defaults, this
syntax is more compact. It's related to the recent proposal on the
mailing list that
List<@Nullable T>
should mean the same as
List<T extends @Nullable Object>
I'm open to better suggestions, and to comments on these.
-Mike