-
Notifications
You must be signed in to change notification settings - Fork 299
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
Adding support for nullable type upper bounds considering Library mod… #893
Conversation
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## master #893 +/- ##
============================================
+ Coverage 86.95% 87.07% +0.12%
- Complexity 1953 1981 +28
============================================
Files 77 77
Lines 6315 6385 +70
Branches 1222 1239 +17
============================================
+ Hits 5491 5560 +69
+ Misses 420 419 -1
- Partials 404 406 +2 ☔ View full report in Codecov by Sentry. |
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.
The approach mostly looks good! I just have one comment on the API structure.
LibraryModelsHandler libHandler = new LibraryModelsHandler(config); | ||
boolean hasNullableUpperBoundLibModel = | ||
libHandler.nullableUpperBoundExistsInLibModel(baseType.tsym.toString(), i); |
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.
We don't want to create a new LibraryModelsHandler
object here. To be consistent with the rest of the code, I think what we want here is to add a new method to the Handler
interface like onOverrideTypeParameterUpperBound
. We can implement that method in LibraryModelsHandler
. Then we could pass in the handler
object from the NullAway
class and then call handler.onOverrideTypeParameterUpperBound
here.
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.
Thanks for the comment. I agree, I didn't want to add the method to the interface till I was sure this is the right approach. I'll add it now.
…and updating relevant implementations.
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.
Couple of quick comments, will look more closely later
* @return map from the className to the position of the generic type argument that has a Nullable | ||
* upper bound. | ||
*/ | ||
ImmutableSetMultimap<String, Integer> nullableVariableTypeUpperBounds(); |
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.
ImmutableSetMultimap<String, Integer> nullableVariableTypeUpperBounds(); | |
ImmutableSetMultimap<String, Integer> typeVariablesWithNullableUpperBounds(); |
@@ -97,9 +103,11 @@ public static void checkInstantiationForParameterizedTypedTree( | |||
upperBound.getAnnotationMirrors(); | |||
boolean hasNullableAnnotation = | |||
Nullness.hasNullableAnnotation(annotationMirrors.stream(), config); | |||
boolean hasNullableUpperBoundLibModel = |
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.
I don't think we need a new variable here. We could just do hasNullableAnnotation = Nullness.hasNullableAnnotation(annotationMirrors.stream(), config) || handler.onOverrideTypeParameterUpperBound(baseType.tsym.toString(), i)
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.
Done
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.
a few more comments
* @return map from the className to the position of the generic type argument that has a Nullable | ||
* upper bound. | ||
*/ | ||
ImmutableSetMultimap<String, Integer> typeVariablesWithNullableUpperBounds(); |
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.
So, thinking more about this change, one thing is that thus far, JSpecify mode changes have all been hidden under a flag and current NullAway users have not been impacted at all. This change would be different, since anyone who built their own LibraryModels
implementation will need to implement this method. But, the LibraryModels
interface is where this method belongs. I am leaning towards allowing this method here, but maybe we should also provide an abstract class like EmptyLibraryModels
that users can subclass, where it would just return empty for everything. What do you think?
Also, maybe we should be more careful about version numbering, and make the next release 0.11.0 to indicate there is a "breaking" change for users who implemented the LibraryModels
interface before. @yuxincs do you agree?
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.
So should we also extend the new Empty library models abstract class for the classes currently implementing LibraryModels interface. for e.g the Example Library Models? So that the default implementations are kept in the EmptyLibraryModels.
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.
Yeah, that's what I'm thinking. Our current implementations should extend the new empty library models class.
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.
Thanks. Makes sense, I'll do that.
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.
Ok, really sorry here, but I realized we are going about this the wrong way, and we really should be using the language feature introduced for just this case, default methods in interfaces. There is no need for introducing the EmptyLibraryModels
class; we should just make typeVariablesWithNullableUpperBounds()
a default
method that returns the empty set and then all existing implementations will just work without any changes.
@akulk022 could you do the following:
- Make
typeVariablesWithNullableUpperBounds()
adefault
method. - Also make
LibraryModels.nullableFields()
adefault
method that returns an empty set. We should have done this when introducing this method before but I forgot. - Get rid of
EmptyLibraryModels
, and undo the changes toExampleLibraryModels
andTestLibraryModels
.
Sorry again for not remembering about default
methods but they are definitely the best way to go here.
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.
No problem, It's a nice language feature to deal with such a problem and I should have known it exists, I will certainly remember it now. I'll do the needful right away.
@@ -305,4 +305,16 @@ public Integer castToNonNullArgumentPositionsForMethod( | |||
} | |||
return previousArgumentPosition; | |||
} | |||
|
|||
@Override | |||
public boolean onOverrideTypeParameterUpperBound(String className, int arg) { |
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.
Document in Javadoc that this returns true if any handler returns true
* @return boolean value of regardless of whether the upper bound for that argument has a Nullable | ||
* upper bound. | ||
*/ | ||
boolean onOverrideTypeParameterUpperBound(String className, int arg); |
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.
boolean onOverrideTypeParameterUpperBound(String className, int arg); | |
boolean onOverrideTypeParameterUpperBound(String className, int index); |
for (Map.Entry<String, Integer> entry : | ||
libraryModels.typeVariablesWithNullableUpperBounds().entries()) { | ||
nullableVariableTypeUpperBoundsBuilder.put(entry); | ||
} |
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.
Use putAll
instead of writing a loop
private static class OptimizedLibraryModels { | ||
|
||
/** | ||
* Mapping from {@link MethodRef} to some state, where lookups first check for a matching method | ||
* name as an optimization. The {@link Name} data structure is used to avoid unnecessary String | ||
* conversions when looking up {@link com.sun.tools.javac.code.Symbol.MethodSymbol}s. | ||
* conversions when looking up {@link Symbol.MethodSymbol}s. |
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.
Why this change?
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.
I am not sure why this went in there, It was probably the IDE that did something, I'll revert it.
"import java.util.function.Function;", | ||
"class Test {", | ||
" static void testNegative() {", | ||
" Function<String,@Nullable String> removeA = a -> a.replace(\"a\",\"\");", |
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.
Let's write more tests to show the type argument is being handled correctly. E.g., check that passing in @Nullable
when the first type argument is not @Nullable
yields an error, etc.
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 comment is still not addressed
@Override | ||
public ImmutableSetMultimap<String, Integer> typeVariablesWithNullableUpperBounds() { | ||
return ImmutableSetMultimap.of(); | ||
} | ||
|
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.
Code like in this class could be the EmptyLibraryModels
implementation maybe
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.
@akulk022 we can now delete this method right?
Co-authored-by: Manu Sridharan <[email protected]>
…7' into library_models_Function_abhijitk7 # Conflicts: # nullaway/src/main/java/com/uber/nullaway/handlers/Handler.java
…terface" This reverts commit 81f8b7d. "Reverting EmptyLibraryModels changes"
…ds and nullableFields
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.
A few more comments
@Override | ||
public ImmutableSetMultimap<String, Integer> typeVariablesWithNullableUpperBounds() { | ||
return ImmutableSetMultimap.of(); | ||
} | ||
|
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.
@akulk022 we can now delete this method right?
@Override | ||
public ImmutableSetMultimap<String, Integer> typeVariablesWithNullableUpperBounds() { | ||
return ImmutableSetMultimap.of(); | ||
} | ||
|
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.
With the default
method I think we can delete this
/** | ||
* Called to get the library models which have Nullable upper bound for their Generic Type | ||
* Arguments. | ||
* | ||
* @param className A String containing the name of the class for which we want to check Nullable | ||
* upper bound. | ||
* @param index Indicates the generic type argument for which we want to check Nullable upper | ||
* bound. | ||
* @return boolean value of regardless of whether the upper bound for that argument has a Nullable | ||
* upper bound. | ||
*/ | ||
boolean onOverrideTypeParameterUpperBound(String className, int index); | ||
|
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.
Didn't I suggest some changes in these docs before? Did those changes get lost?
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.
I added that in a javadoc for the implementation of the method inside CompositeHandler since it seemed specific to the implementation inside CompositeHandler, would it be more appropriate to mention it here instead?
new ImmutableSetMultimap.Builder<String, Integer>() | ||
.put("java.util.function.Function", 0) | ||
.put("java.util.function.Function", 1) | ||
.put("java.util.Map", 1) |
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.
I don't recall wanting to model java.util.Map
in this PR. Why did we add this change?
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.
Sorry, I had added it for a positive test case where only one of the indices has the upper bound and we still fail for the other. But in hindsight it is not the correct way to add a model for it. I'll remove it.
nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java
Outdated
Show resolved
Hide resolved
…sHandler.java Co-authored-by: Manu Sridharan <[email protected]>
…and TestLibraryModels
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.
Mostly looks good, just one comment remaining to be addressed
"import java.util.function.Function;", | ||
"class Test {", | ||
" static void testNegative() {", | ||
" Function<String,@Nullable String> removeA = a -> a.replace(\"a\",\"\");", |
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 comment is still not addressed
…els for generic types
…and updating relevant implementations.
…terface" This reverts commit 81f8b7d. "Reverting EmptyLibraryModels changes"
…ds and nullableFields
…and TestLibraryModels
…7' into library_models_Function_abhijitk7 # Conflicts: # nullaway/src/main/java/com/uber/nullaway/CodeAnnotationInfo.java # nullaway/src/main/java/com/uber/nullaway/LibraryModels.java # nullaway/src/main/java/com/uber/nullaway/NullAway.java # nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java # nullaway/src/main/java/com/uber/nullaway/handlers/CompositeHandler.java # nullaway/src/main/java/com/uber/nullaway/handlers/Handler.java # nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java
…7' into library_models_Function_abhijitk7 # Conflicts: # nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
This reverts commit 00a317a.
This reverts commit 986d060.
This reverts commit 115d683.
)" This reverts commit 091ac38
We should skip checking for errors here instead of crashing (cherry picked from commit 091ac38)
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.
Unfortunately something has gone wrong with this PR. It has removed changes that are present in the master branch and those removals are not showing up in the GitHub UI for some reason. Compare this branch's README.md Android section:
With the master branch:
https://github.com/uber/NullAway?tab=readme-ov-file#android
I think the best thing to do is to re-create this PR somehow, to make sure we're not losing master branch changes unintentionally.
@@ -145,10 +147,11 @@ private static boolean isClassFieldOfPrimitiveType(Symbol symbol) { | |||
* | |||
* @param symbol symbol for entity | |||
* @param config NullAway config | |||
* @param handler NullAway Handler |
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.
* @param handler NullAway Handler | |
* @param handler optional NullAway Handler |
@@ -176,12 +179,15 @@ public boolean isSymbolUnannotated(Symbol symbol, Config config) { | |||
* Check whether a class should be treated as nullness-annotated. | |||
* | |||
* @param classSymbol The symbol for the class to be checked | |||
* @param config NullAway config | |||
* @param handler NullAway handler |
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.
* @param handler NullAway handler | |
* @param handler optional NullAway handler |
@@ -190,12 +196,15 @@ public boolean isClassNullAnnotated(Symbol.ClassSymbol classSymbol, Config confi | |||
* <p>This method is recursive, using the cache on the way up and populating it on the way down. | |||
* | |||
* @param classSymbol The class to query, possibly an inner class | |||
* @param config NullAway config | |||
* @param handler NullAway handler |
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.
* @param handler NullAway handler | |
* @param handler optional NullAway handler |
…els (#903) The below line previously reported an error considering the upperbound of the generic type parameter couldn't be Nullable, adding it into library models to allow it to be Nullable passes the test case. ```java Function<String,@nullable String> removeA = a -> a.replace("a",""); ``` We now have library model methods for treating a type as `@NullMarked` and also for making the upper bound of type variables `@Nullable`. We use these new methods to model `java.util.function.Function` as `@NullMarked`, in JSpecify mode only. We fix a related issue with unwanted errors when passing a lambda / method reference to `@NullUnmarked` code; this was necessary to get NullAway itself to still build with NullAway checking enabled. (Due to issues with the branch this PR was recreated from the original #893) --------- Co-authored-by: Manu Sridharan <[email protected]>
The below line previously reported an error considering the upperbound of the generic type parameter couldn't be Nullable, adding it into library models to allow it to be Nullable passes the test case.