-
Notifications
You must be signed in to change notification settings - Fork 351
Use ground target types when handling lambdas and method refs passed to generic methods #1575
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3294616
3f34c93
72efc05
7aba47b
0547150
954ccd5
6f9f0a2
38a6a9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,6 @@ | |
| import com.uber.nullaway.NullAwayTestsBase; | ||
| import com.uber.nullaway.generics.JSpecifyJavacConfig; | ||
| import java.util.Arrays; | ||
| import org.junit.Ignore; | ||
| import org.junit.Test; | ||
|
|
||
| public class WildcardTests extends NullAwayTestsBase { | ||
|
|
@@ -580,7 +579,6 @@ static void test() { | |
| .doTest(); | ||
| } | ||
|
|
||
| @Ignore("https://github.com/uber/NullAway/issues/1522") | ||
| @Test | ||
| public void issue1522() { | ||
| makeHelperWithInferenceFailureWarning() | ||
|
|
@@ -605,6 +603,163 @@ static <T> Foo<T> after(Foo<Optional<T>> foo) { | |
| .doTest(); | ||
| } | ||
|
|
||
| @Test | ||
| public void issue1522SelfContained() { | ||
| makeHelperWithInferenceFailureWarning() | ||
| .addSourceLines( | ||
| "Test.java", | ||
| """ | ||
| import org.jspecify.annotations.*; | ||
| @NullMarked | ||
| class Test { | ||
| interface Function<T extends @Nullable Object, U extends @Nullable Object> { | ||
| U apply(T t); | ||
| } | ||
| static class Optional<T> { | ||
| public @Nullable T orElse(@Nullable T other) { | ||
| throw new RuntimeException(); | ||
| } | ||
| } | ||
| static class Foo<T> { | ||
| public final <V> Foo<V> mapNotNull(Function<? super T, ? extends @Nullable V> mapper) { | ||
| throw new RuntimeException(); | ||
| } | ||
| } | ||
| static <T> Foo<T> after(Foo<Optional<T>> foo) { | ||
| return foo.mapNotNull(x -> x.orElse(null)); | ||
| } | ||
| } | ||
| """) | ||
| .doTest(); | ||
| } | ||
|
|
||
| @Test | ||
| public void issue1522SelfContainedWithMethodReference() { | ||
| makeHelperWithInferenceFailureWarning() | ||
| .addSourceLines( | ||
| "Test.java", | ||
| """ | ||
| import org.jspecify.annotations.*; | ||
| @NullMarked | ||
| class Test { | ||
| interface Function<T extends @Nullable Object, U extends @Nullable Object> { | ||
| U apply(T t); | ||
| } | ||
| static class Optional<T> { | ||
| public @Nullable T orElse(@Nullable T other) { | ||
| throw new RuntimeException(); | ||
| } | ||
| } | ||
| static class Foo<T> { | ||
| public final <V> Foo<V> mapNotNull(Function<? super T, ? extends @Nullable V> mapper) { | ||
| throw new RuntimeException(); | ||
| } | ||
| } | ||
| static <T> @Nullable T orElseNull(Optional<T> optional) { | ||
| return optional.orElse(null); | ||
| } | ||
| static <T> Foo<T> after(Foo<Optional<T>> foo) { | ||
| return foo.mapNotNull(Test::orElseNull); | ||
| } | ||
| } | ||
| """) | ||
| .doTest(); | ||
| } | ||
|
|
||
| @Test | ||
| public void groundTargetTypePreservesNestedWildcards() { | ||
| makeHelperWithInferenceFailureWarning() | ||
| .addSourceLines( | ||
| "Test.java", | ||
| """ | ||
| import org.jspecify.annotations.*; | ||
| @NullMarked | ||
| class Test { | ||
| interface Function<T extends @Nullable Object, R extends @Nullable Object> { | ||
| R apply(T t); | ||
| } | ||
| static class Box<T extends @Nullable Object> { | ||
| T get() { | ||
| throw new RuntimeException(); | ||
| } | ||
| } | ||
| static <R extends @Nullable Object> R invokeNested( | ||
| Function<Box<? super String>, R> mapper) { | ||
| throw new RuntimeException(); | ||
| } | ||
| static <R extends @Nullable Object> R invokeNestedWithUpperBound( | ||
| Function<Box<? extends String>, R> mapper) { | ||
| throw new RuntimeException(); | ||
| } | ||
| static <R extends @Nullable Object> R invokeTopLevelWildcard( | ||
| Function<? super Box<? super String>, R> mapper) { | ||
| throw new RuntimeException(); | ||
| } | ||
| static <R extends @Nullable Object> R invokeArray( | ||
| Function<Box<? super String>[], R> mapper) { | ||
| throw new RuntimeException(); | ||
| } | ||
| static void testNestedWildcard() { | ||
| invokeNested(box -> { | ||
| // BUG: Diagnostic contains: dereferenced expression box.get() is @Nullable | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does the true negative case look like here? Changing
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's correct. I've enhanced the test to show this. |
||
| box.get().hashCode(); | ||
| return null; | ||
| }); | ||
| invokeNestedWithUpperBound(box -> { | ||
| // safe since the upper bound of the Box type variable is @NonNull String, | ||
| // so box.get() cannot be null | ||
| box.get().hashCode(); | ||
| return null; | ||
| }); | ||
| } | ||
| static void testTopLevelWildcardBound() { | ||
| invokeTopLevelWildcard(box -> { | ||
| // BUG: Diagnostic contains: dereferenced expression box.get() is @Nullable | ||
| box.get().hashCode(); | ||
| return null; | ||
| }); | ||
| } | ||
| static void testArrayWithNestedWildcard() { | ||
| invokeArray(boxes -> { | ||
| // BUG: Diagnostic contains: dereferenced expression boxes[0].get() is @Nullable | ||
| boxes[0].get().hashCode(); | ||
| return null; | ||
| }); | ||
| } | ||
| } | ||
| """) | ||
| .doTest(); | ||
| } | ||
|
|
||
| @Test | ||
| public void groundTargetTypePreservesNestedWildcardsForMethodReferences() { | ||
| makeHelperWithInferenceFailureWarning() | ||
| .addSourceLines( | ||
| "Test.java", | ||
| """ | ||
| import org.jspecify.annotations.*; | ||
| @NullMarked | ||
| class Test { | ||
| interface Function<T extends @Nullable Object, R extends @Nullable Object> { | ||
| R apply(T t); | ||
| } | ||
| static class Box<T extends @Nullable Object> {} | ||
| static <R extends @Nullable Object> R invokeExtendsNullable( | ||
| Function<Box<? extends @Nullable String>, R> mapper) { | ||
| throw new RuntimeException(); | ||
| } | ||
| static @Nullable Object needsBoxExtendsString(Box<? extends String> box) { | ||
| return null; | ||
| } | ||
| static void test() { | ||
| // BUG: Diagnostic contains: parameter type of referenced method is Box<? extends String> | ||
| invokeExtendsNullable(Test::needsBoxExtendsString); | ||
| } | ||
| } | ||
| """) | ||
| .doTest(); | ||
| } | ||
|
|
||
| /** | ||
| * Extracted from Caffeine; exposed some subtle bugs in substitutions involving identity of {@code | ||
| * Type} objects | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works the same regardless of whether we are looking at the types used for the arguments of the function vs the return argument of the function? This looks correct to me from 9.9, but feels a bit counter-intuitive...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's correct. It was counterintuitive to me too. It's basically strictly on the bounds of the wildcard type, independently of where it is used.