NullAway icon indicating copy to clipboard operation
NullAway copied to clipboard

JSpecify: support inference for calls to generic methods

Open msridhar opened this issue 1 year ago • 9 comments

This will be a significant challenge in general, but maybe we can handle the common cases and do something useful without having a full technique. See here for one test case:

https://github.com/uber/NullAway/blob/7a3d08895e2960adeb94f0d2d6d1205ae1c142d9/nullaway/src/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java#L141-L160

msridhar avatar Nov 14 '24 00:11 msridhar

Hit this as well.

The specific case I ran into was in this form:

@NullMarked
public class Foo {
  public static class Key<T extends @Nullable Object> {}

  private static final Key<@Nullable Foo> KEY = new Key();

  public static <T extends @Nullable Object> void setValue(Key<T> key, T value) {
  }

  public static void main() {
    setValue(KEY, null);
  }
}

agrieve avatar Jan 08 '25 15:01 agrieve

Thanks for the example! We are working on this.

msridhar avatar Jan 08 '25 15:01 msridhar

FYI, this (or something very similar) came up when Dropwizard upgraded to a new version of Caffeine. Fortunately, in their case, they appear to be better off just removing their usage of @Nullable.

cpovirk avatar Jan 23 '25 18:01 cpovirk

Heh, this is coming up more with the new release because now our annotations within generics are being noticed more :P.

Here's a simpler example:

import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;

@NullMarked
public class Foo {

  public static void test(@Nullable Object foo) {
      helper(foo);
  }

  private static <T extends @Nullable Object> void helper(T value) {
      System.out.println(value);
  }
}
Foo.java:10: warning: [NullAway] passing @Nullable parameter 'foo' where @NonNull is required
      helper(foo);
             ^
    (see http://t.uber.com/nullaway )

The best work-arounds I've found so far are:

  1. Use explicit generics at callsite:
  public static void test(@Nullable Object foo) {
      Foo.<@Nullable Object>helper(foo);
  }
  1. Marking the helper as @NullUnmarked:
  @NullUnmarked
  private static <T extends @Nullable Object> void helper(T value) {
      System.out.println(value);
  }

Just sharing in case it's helpful to anyone else.

agrieve avatar Mar 04 '25 15:03 agrieve

I hope that #1131 will cover many common cases here, but it's a subtle change and we're still working on it. @agrieve once that PR is landed I may ask if you can test on Chromium with a snapshot build.

I'm glad to hear that explicit generics have worked for you. That what I would have hoped / expected, though we definitely aim to eliminate the need for these as much as possible.

msridhar avatar Mar 04 '25 18:03 msridhar

Actually, small correction, we will handle the specific case from https://github.com/uber/NullAway/issues/1075#issuecomment-2698009946 in a follow-up to #1131

msridhar avatar Mar 04 '25 18:03 msridhar

Not sure if that's the same one, but with:

public class Wrapper<T extends @Nullable String> {

	private final T value;
	
	public Wrapper(T value) {
		this.value = value;
	}

	T unwrap() {
		return value;
	}
}

I am unable to write new Wrapper<@Nullable String>(null) and currently have to specify @NullUnmarked public Wrapper(T value)to make it work.

sdeleuze avatar Apr 10 '25 12:04 sdeleuze

@sdeleuze I think your latest case may be #1155 rather than this one? I'll try to look at that one soon.

msridhar avatar Apr 10 '25 13:04 msridhar

Indeed, looks like more related to #1155. Thanks!

sdeleuze avatar Apr 10 '25 14:04 sdeleuze

is this the same case or is this a separate issue?

static <R> void invoke(Supplier<@Nullable R> supplier) {
}
invoke(() -> null);
error: [NullAway] Cannot pass parameter of type Supplier<Object>, as formal parameter has type Supplier<@org.jspecify.annotations.Nullable R>, which has mismatched type parameter nullability
        invoke(() -> null);
               ^

mhalbritter avatar Aug 01 '25 06:08 mhalbritter

@mhalbritter I'm afraid it is, you can try this as a workaround:

@NullUnmarked
static <R extends @Nullable Object> void invoke(@NonNull Supplier<R> supplier) {
}

jonatan-ivanov avatar Aug 01 '25 21:08 jonatan-ivanov

Hi all, we've made significant improvements in generic method inference on the master branch, which should be released soon in version 0.12.10. I think we're at the point where we should open follow-up issues for specific limitations, to track things more carefully, and we can close this issue on initial support. I've gone through the reports here and opened up one follow-up issue, #1290. Also, #1157 needs to be re-opened. As far as I could see, the other examples reported here work now. We welcome follow-up issues for cases that still don't work! I'll comment back when the release is out.

msridhar avatar Sep 16 '25 17:09 msridhar

NullAway 0.12.10 is now released with these changes.

msridhar avatar Sep 16 '25 18:09 msridhar