-
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
Issue #740: Adding visitors for handling different types along with ClassType in Generic Type invariance check #806
Conversation
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.
Looks very promising! These are a couple of quick comments from a very quick initial review.
public class PreservedAnnotationTreeVisitor extends SimpleTreeVisitor<Type, Boolean> { | ||
@Override | ||
public Type visitArrayType(ArrayTypeTree tree, Boolean p) { | ||
ParameterizedTypeTree paramTree = (ParameterizedTypeTree) tree.getType(); |
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.
Are we sure tree.getType()
will always be a ParameterizedTypeTree
?
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, I tried running a test with a regular ArrayType inside the generic like A<String[]> and that ran fine. Maybe its better to just do Type.ClassType classType = (Type.ClassType) tree.getType().accept(new PreservedAnnotationTreeVisitor(), null); return new Type.ArrayType(classType, classType.tsym);
instead of assuming?
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.
Can you try a type like A<int[]>
? If we can't find a counterexample we can leave this code as is
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.
It gives a ClassCastException in the TYPE_ARG_VISITOR since it cannot cast PrimitiveType to ClassType
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 then we need a fix somewhere. Please add that as another test case as well
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.
It was a simple fix to not cast it into ClassType on those lines, as far as this piece of code is concerned, I just realised that we do not accept this Visitor unless we have generic type arguments so we never even reach this method in those situations.
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.
Not sure what the latest is here, but let's see what happens after we add the other tests I suggested
}; | ||
|
||
/** Visitor For Handling Different Tree Types */ | ||
public class PreservedAnnotationTreeVisitor extends SimpleTreeVisitor<Type, Boolean> { |
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 is it SimpleTreeVisitor<Type, Boolean>
and not SimpleTreeVisitor<Type, Void>
? What is the Boolean
used for?
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.
It should be Void, I do not recall why it was Boolean but it certainly doesn't make sense now, Thanks for catching that.
Co-authored-by: Manu Sridharan <[email protected]>
…022/NullAway into generic_type_issue_#740_ak # Conflicts: # nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java
…#740_ak # Conflicts: # nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java
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.
Here are some more detailed comments
nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java
Show resolved
Hide resolved
List<Type> genericArgType = currentTypeArgType.accept(TYPE_ARG_VISITOR, null); | ||
if (genericArgType.size() > 0) { | ||
currentTypeArgType = curTypeArg.accept(new PreservedAnnotationTreeVisitor(state), null); | ||
} |
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 think this logic is checking, if there type arguments nested at any depth within currentTypeArgType
, then we need to recurse and preserve type annotations there. It makes sense at a high level, but I'm not sure this is really an optimization (since we are adding another traversal of the type), and I'm not sure the logic in TYPE_ARG_VISITOR
is correct (it doesn't seem to recursively apply the visitor). For now, how about we remove this optimization, and just unconditionally always do currentTypeArgType = curTypeArg.accept(new PreservedAnnotationTreeVisitor(state), null);
?
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 purpose of TYPE_ARG_VISITOR is just to be able to get the type arguments even if the Argument is not a ClassType, but the if condition checking the size is greater than 0 was already there as given below, do you want me to remove that logic?
if (currentTypeArgType.getTypeArguments().size() > 0) { // nested generic type; recursively preserve its nullability type argument annotations currentTypeArgType = typeWithPreservedAnnotations((ParameterizedTypeTree) curTypeArg, state); }
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.
You mentioned you get an NPE without this check. What is the stack trace for 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.
One of the test cases that failed is: A<A<@Nullable String>[]> var = new A<A<String>[]>();
, when we traverse the ParametrizedTypeTree on the RHS (A<A[]>), when we get to the String, since we don't check if there are further nested arguments and accept the visitor, we get a null for currentTypeArgType, so we would certainly need some type of a check to make sure we don't run the logic again if we don't have further generic types.
Stack Trace:
java.lang.NullPointerException: Cannot invoke "com.sun.tools.javac.code.Type.cloneWithMetadata(com.sun.tools.javac.code.TypeMetadata)" because "currentTypeArgType" is null
at com.uber.nullaway.GenericsChecks$PreservedAnnotationTreeVisitor.visitParameterizedType(GenericsChecks.java:654)
at com.uber.nullaway.GenericsChecks$PreservedAnnotationTreeVisitor.visitParameterizedType(GenericsChecks.java:595)
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 fixed this by overriding the defaultAction
method in b9eb8fc
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.
That is a good idea, Thanks!
@@ -577,6 +529,182 @@ public static void compareGenericTypeParameterNullabilityForCall( | |||
} | |||
} | |||
|
|||
/** To dispatch the logic to obtain the generic type arguments for different Types */ |
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.
Assuming we agree with my suggestion above, this visitor can be deleted
} | ||
}; | ||
|
||
/** To dispatch the logic to0 obtain the generic type arguments for different Types */ |
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 seems wrong
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 it is wrong, it should be fixed in the newer commits, Thanks!
// The base type of rhsType may be a subtype of lhsType's base type. In such cases, we | ||
// must | ||
// compare lhsType against the supertype of rhsType with a matching base type. |
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.
Nit: formatting
// This is impossible, considering the fact that standard Java subtyping succeeds before | ||
// running | ||
// NullAway |
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.
Nit: formatting
List<Type> genericArgs = lhsTypeArgument.accept(TYPE_ARG_VISITOR, null); | ||
if (genericArgs.size() > 0) { |
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.
Again let's get rid of this optimization and just unconditionally do a recursive traversal
} | ||
} | ||
|
||
/** Visitor For Handling Different Tree Types */ |
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 need a more descriptive comment
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 agree, I have added a more descriptive comment
public class PreservedAnnotationTreeVisitor extends SimpleTreeVisitor<Type, Boolean> { | ||
@Override | ||
public Type visitArrayType(ArrayTypeTree tree, Boolean p) { | ||
ParameterizedTypeTree paramTree = (ParameterizedTypeTree) tree.getType(); |
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.
Not sure what the latest is here, but let's see what happens after we add the other tests I suggested
…n certain comments
… generic type arguments before recursing further
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.
very minor
Co-authored-by: Manu Sridharan <[email protected]>
Co-authored-by: Manu Sridharan <[email protected]>
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 think this is ready to land! Will check if @lazaroclapp wants to take a look
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.
Overall looks good to me. One suggestion regarding test cases and a possible follow-up refactoring option, but otherwise this looks fine by me :)
" static class A<T extends @Nullable Object> { }", | ||
" static void testPositive() {", | ||
" // BUG: Diagnostic contains: Cannot assign from type", | ||
" A<A<@Nullable String>[]> var = new A<A<String>[]>();", |
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.
How about adding the:
A<A<String>[]> var = new A<A<@Nullable String>[]>();
and
A<A<String>[]> var = new A<A<String>[]>();
cases?
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.
* Nullability annotations for the nested generic type arguments. Compares the Type it is called | ||
* upon, i.e. the LHS type and the Type passed as an argument, i.e. The RHS type. | ||
*/ | ||
public static class CompareNullabilityVisitor extends Types.DefaultTypeVisitor<Boolean, Type> { |
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.
Fine with these being inner classes for now, but a good follow up PR would be a simple refactor of this into a com.uber.nullaway.generics
package with three top level classes (preferably in its own PR without any other changes, so it's easier to review).
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.
Created #817, agreed this is good for a follow up
@@ -908,6 +964,7 @@ public String visitCapturedType(Type.CapturedType t, Void s) { | |||
|
|||
@Override | |||
public String visitArrayType(Type.ArrayType t, Void unused) { | |||
// TODO properly print cases like int @Nullable[] |
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 should also fix printing of these types in a follow-up
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.
See #818
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.
LGTM! 🚀
Description: Only ClassTypes are supported for checking the invariance of the nullability annotations for Generic types and hence TypeVisitors and a TreeVisitor are added to handle different scenarios like for example:
Foo<Foo<@Nullable String>[]> x = new Foo<Foo<String>[]>();
Issue Number: 740
All the tests in NullAwayJSpecifyGenericTests.java have passed for these changes.
Fixes #740