Skip to content

Conversation

schlosna
Copy link
Contributor

Before this PR

We were not using the full JSpecify mode of NullAway and several packages were not fully @NullMarked.

After this PR

==COMMIT_MSG==
All tritium packages are fully @NullMarked with jspecify @Nullable or @NonNull annotations as needed by APIs. Enable JSpecify mode for stricter NullAway nullness checking.

See https://github.com/uber/NullAway/wiki/JSpecify-Support and https://github.com/uber/NullAway/wiki/How-NullAway-Works#jspecify
==COMMIT_MSG==

Possible downsides?

@changelog-app
Copy link

changelog-app bot commented Sep 20, 2025

Generate changelog in changelog/@unreleased

Type (Select exactly one)

  • Feature (Adding new functionality)
  • Improvement (Improving existing functionality)
  • Fix (Fixing an issue with existing functionality)
  • Break (Creating a new major version by breaking public APIs)
  • Deprecation (Removing functionality in a non-breaking way)
  • Migration (Automatically moving data/functionality to a new system)

Description

All tritium packages are fully @NullMarked with jspecify @Nullable or @NonNull annotations as needed by APIs. Enable JSpecify mode for stricter NullAway nullness checking.

See https://github.com/uber/NullAway/wiki/JSpecify-Support and https://github.com/uber/NullAway/wiki/How-NullAway-Works#jspecify

Check the box to generate changelog(s)

  • Generate changelog entry

@changelog-app
Copy link

changelog-app bot commented Sep 20, 2025

Successfully generated changelog entry!

What happened?

Your changelog entries have been stored in the database as part of our migration to ChangelogV3.

Need to regenerate?

Simply interact with the changelog bot comment again to regenerate these entries.

@schlosna schlosna force-pushed the davids/NullAway-JSpecifyMode branch from 54101d3 to acffc69 Compare September 20, 2025 21:00
static class CompositeInvocationContext extends DefaultInvocationContext {

private final InvocationContext[] contexts;
private final @Nullable InvocationContext @NonNull [] contexts;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with jspecify this is equivalent to saying that the contexts array itself is not null; however, the InvocationContext elements may be null.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are pretty confusing to be honest. I can't imagine a better way to annotate these (maybe it would have been nicer to have a @NullableElements annotations, but I don't imagine these is one). For what it's worth, I've always assumed the annotation to apply to the field itself, so I would likely naturally read this the exact opposite (@Nullable applying to the field, and @NonNull applying to the elements in the array)

It's clearer when you read through https://jspecify.dev/docs/user-guide/#type-use-annotation-syntax but can certainly get folks to shoot themselves in the foot :(

It's unfortunate that this pattern is in so many places, as I think a quick comment explaining what this means would be great here.

For instance, below,

        @Nullable
        InvocationContext @NonNull [] getContexts() {

clearly reads to me as the return value of getContexts()` can be null.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it work if we move the @NonNull annotation to the field instead?

Copy link
Contributor Author

@schlosna schlosna Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to switch this back to draft as there are some places that were not previously annotated correctly and some places where NullAway does not seem to properly handle propagation with arrays in jspecify mode right now.

String key = keys[i];
values[valuesIndex] = key;
values[valuesIndex + 1] = data.get(key);
values[valuesIndex + 1] = Strings.nullToEmpty(data.get(key));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a slight behavior change where previously we may have allowed null values through, we now convert to an empty string tag value "", though previously we would likely trigger a NullPointerException when copying the tag map to an ImmutableMap<String, String>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like silently replacing null with the empty string. These are semantically different values and this implicit conversion can be confusing for consumers.

static class CompositeInvocationContext extends DefaultInvocationContext {

private final InvocationContext[] contexts;
private final @Nullable InvocationContext @NonNull [] contexts;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are pretty confusing to be honest. I can't imagine a better way to annotate these (maybe it would have been nicer to have a @NullableElements annotations, but I don't imagine these is one). For what it's worth, I've always assumed the annotation to apply to the field itself, so I would likely naturally read this the exact opposite (@Nullable applying to the field, and @NonNull applying to the elements in the array)

It's clearer when you read through https://jspecify.dev/docs/user-guide/#type-use-annotation-syntax but can certainly get folks to shoot themselves in the foot :(

It's unfortunate that this pattern is in so many places, as I think a quick comment explaining what this means would be great here.

For instance, below,

        @Nullable
        InvocationContext @NonNull [] getContexts() {

clearly reads to me as the return value of getContexts()` can be null.

@Override
@Nullable
public final Object invoke(Object proxy, Method method, @Nullable Object[] nullableArgs) throws Throwable {
public final Object invoke(Object proxy, Method method, Object @Nullable [] nullableArgs) throws Throwable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an example of what I described in my other comment as being confusing. And makes me wonder if places like

@NonNull Object instance, @NonNull Method method, @NonNull Object[] args) {
are correct or not

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should actually be:

Suggested change
public final Object invoke(Object proxy, Method method, @Nullable Object[] nullableArgs) throws Throwable {
public final Object invoke(Object proxy, Method method, Object @Nullable [] nullableArgs) throws Throwable {
public final Object invoke(Object proxy, Method method, @Nullable Object @Nullable [] nullableArgs) throws Throwable {

@schlosna schlosna removed the request for review from pkoenig10 September 22, 2025 13:26
@schlosna schlosna marked this pull request as draft September 22, 2025 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants