agents/skills/nullaway/SKILL.md
@NullMarked vs New CodeThe approach differs significantly depending on whether you are migrating
existing code to @NullMarked or writing new code in an already @NullMarked
context:
@NullMarked:
NullUtil.assumeNonNull() when dereferencing immediately
to satisfy the analyzer without runtime overhead. Use assert when storing
or returning values. Use if guards occasionally if it helps avoid
assertions, but avoid changing the logic.@NullMarked code):
asserts to enforce contracts. Rely on
correct annotations (or their absence for implicit @NonNull) and only use
null guards for @Nullable values. assumeNonNull() should generally not
be used for new code.assumeNonNull vs assert != nullThe choice between assumeNonNull and Java assert depends on how the value is
used:
@Nullable values immediately: Use
NullUtil.assumeNonNull(x).
var value = mSupplier.get();
assumeNonNull(value);
value.doSomething();
assumeNonNull
is a no-op that satisfies the analyzer without adding redundant runtime
checks.assumeNonNull() should be on a separate line, almost
always, rather than used inline.assumeNonNull().@Nullable values to non-null parameters: First, look into the
call tree of the receiver class and see if that parameter should be
annotated as @Nullable or not. If it can natively handle null, update the
method signature instead of adding an assertion. If it strictly requires a
non-null value, THEN you MUST use a Java assert x != null; on a preceding
line before passing or storing it.
[!CAUTION] NEVER use
assumeNonNull(x)to pass a@Nullablevalue to a non-null parameter or to return it from a non-nullable method. This is STRICTLY FORBIDDEN. You MUST use a Javaasserton a preceding line to add a runtime check.
mReceiver.setSomething(assumeNonNull(nullableValue));
assert nullableValue != null;
mReceiver.setSomething(nullableValue);
@Nullable values from non-nullable methods: First, consider if
the method's return type can be safely updated to @Nullable. If it cannot
(e.g., because it implements an interface or strictly enforces a non-null
contract), you MUST use a Java assert x != null; on a preceding line before
returning the value. Using assumeNonNull(x) inline within a return statement
is strictly forbidden.
return assumeNonNull(nullableValue);
assert nullableValue != null;
return nullableValue;
@Nullable, prefer updating the method
signature over adding an assertion.assert value != null on Supplier.get()
during initialization. If the supplier value is set LATER (as is common with
UI wiring), the assert might fail immediately during construction. In such
cases, the getter should return @Nullable and callers should handle it,
rather than asserting non-null immediately.Supplier<@Nullable T> to a constructor that expects
Supplier<T> (non-nullable), or vice versa.Supplier<@Nullable T> rather
than wildcards like Supplier<? extends @Nullable T> in method signatures and
fields.SupplierUtils.upcast() is strictly for upcasting the type
parameter to a base class (e.g., Supplier<DerivedT> to Supplier<BaseT>).
Do NOT use it solely for handling nullability differences (e.g., Supplier<T>
to Supplier<@Nullable T>).Supplier<T> to Supplier<@Nullable T>) if you are passing around subclasses
of ObservableSupplier.Supplier<@Nullable T> and handle the nullity. Never use
hacks or assertions to force a Supplier<@Nullable T> to act as a
non-nullable Supplier<T>.Supplier<@Nullable T>.
assumeNonNull or assert).() -> assumeNonNull(supplier.get()) or this:
() -> { var x = getter(); assert x != null; return x; }. These are
anti-patterns.
ObservableSupplier: Pass supplier.asNonNull() directly.
This returns a NonNullObservableSupplier which satisfies
Supplier<@NonNull T>.Supplier<@Nullable T> and handle the nullity inside the receiver class. Do
not force non-nullability at the call site with hacks.Supplier<@Nullable T> or MonotonicObservableSupplier<T> in method
signatures to avoid forcing non-nullability on callers.org.chromium.build.annotations.Nullable and
org.chromium.build.annotations.NullMarked. Do NOT use androidx.annotation
or javax.annotation variations.@NullMarked: Apply to the class level when you are ready to make the
whole class null-safe.@Nullable: Apply to fields, parameters, and return types that can be
null.@NonNull Default: Values are @NonNull by default in a @NullMarked
class. Do NOT use @NonNull explicitly on fields, parameters, or return
types. Use @NonNull only in the context of nullable generic parameters if
absolutely necessary.@SuppressWarnings("NullAway"):
destroy() or onDestroy(): If fields are
nulled out during teardown to prevent memory leaks, do NOT mark the fields
as @Nullable just to satisfy this one assignment. Instead, mark the fields
as @MonotonicNonNull (if late-initialized) or @NonNull (if initialized
in constructor), and add @SuppressWarnings("NullAway") to the destroy()
or onDestroy() method. This prevents having to null-check the fields
everywhere else in the class.@Nullable
field, the parameter itself should usually be marked @Nullable as well, even
if it is not immediately used as nullable in the constructor. This avoids
artificial non-null requirements at construction time.@Nullable, look at
how callers handle the return value:
@Nullable.Before (in Caller):
mReceiver = new Receiver(() -> assumeNonNull(nullableSupplier.get()));
After:
// Change constructor to take exact Supplier<@Nullable Item>
public Receiver(Supplier<@Nullable Item> supplier) {
mSupplier = supplier;
}
// In usage (Dereferenced right away)
void doSomething() {
var item = mSupplier.get();
assumeNonNull(item);
item.use();
}
// In usage (Stored or Passed)
void storeItem() {
var item = mSupplier.get();
assert item != null;
mStoredItem = item;
}
Supplier<DerivedItem> and the receiver
expects Supplier<@Nullable BaseItem>, use SupplierUtils.upcast() to pass
it:
mReceiver = new Receiver(SupplierUtils.upcast(derivedSupplier, BaseItem.class));
Supplier<@Nullable Item>, pass it directly:
mReceiver = new Receiver(nullableSupplier);
Before applying the recipe above to force non-nullability or add assertions,
investigate the receiver. If the receiver (or classes it passes the supplier
to) already checks for null or can easily be updated to handle null, prefer
updating the signature to accept Supplier<@Nullable T> instead of forcing
non-nullability.
supplier.asNonNull().get() over var x = supplier.get(); assert x != null;.assumeNonNull(object) from
org.chromium.build.NullUtil instead of assert object != null when you want
to chain calls on the non-null object (e.g.,
assumeNonNull(mLayoutManager).getSomething()). It returns the non-null
object.if (x != null) to assert x != null).get*ForTesting() methods should just return @Nullable
(and be annotated as such) rather than asserting non-null, if the underlying
field is nullable. Let the test handle the nullity.PublicTransitLeakTest as a smoke test locally before
running all tests on CQ to validate functional changes introduced by
assertions.@Nullable or @MonotonicNonNull if
it's initialized later (e.g., in init or initWithProfile).@Nullable to
the method signature.assumeNonNull(null) to satisfy
a callback that expects a non-null value if the value can actually be null.
Update the callback definition to accept @Nullable T.