Skip to content
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

Merged
merged 31 commits into from
Aug 30, 2023

Conversation

akulk022
Copy link
Collaborator

@akulk022 akulk022 commented Aug 14, 2023

  • 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

@CLAassistant
Copy link

CLAassistant commented Aug 14, 2023

CLA assistant check
All committers have signed the CLA.

@msridhar msridhar marked this pull request as draft August 14, 2023 19:38
Copy link
Collaborator

@msridhar msridhar left a 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();
Copy link
Collaborator

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?

Copy link
Collaborator Author

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?

Copy link
Collaborator

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

Copy link
Collaborator Author

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

Copy link
Collaborator

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

Copy link
Collaborator Author

@akulk022 akulk022 Aug 16, 2023

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.

Copy link
Collaborator

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> {
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator

@msridhar msridhar left a 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

Comment on lines 412 to 415
List<Type> genericArgType = currentTypeArgType.accept(TYPE_ARG_VISITOR, null);
if (genericArgType.size() > 0) {
currentTypeArgType = curTypeArg.accept(new PreservedAnnotationTreeVisitor(state), null);
}
Copy link
Collaborator

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);?

Copy link
Collaborator Author

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); }

Copy link
Collaborator

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?

Copy link
Collaborator Author

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)

Copy link
Collaborator

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

Copy link
Collaborator Author

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 */
Copy link
Collaborator

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 */
Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment seems wrong

Copy link
Collaborator Author

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!

Comment on lines 567 to 569
// 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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: formatting

Comment on lines 579 to 581
// This is impossible, considering the fact that standard Java subtyping succeeds before
// running
// NullAway
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: formatting

Comment on lines 611 to 612
List<Type> genericArgs = lhsTypeArgument.accept(TYPE_ARG_VISITOR, null);
if (genericArgs.size() > 0) {
Copy link
Collaborator

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 */
Copy link
Collaborator

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

Copy link
Collaborator Author

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();
Copy link
Collaborator

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

Copy link
Collaborator

@msridhar msridhar left a 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 <msridhar@gmail.com>
Co-authored-by: Manu Sridharan <msridhar@gmail.com>
@msridhar msridhar marked this pull request as ready for review August 28, 2023 15:31
Copy link
Collaborator

@msridhar msridhar left a 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

Copy link
Collaborator

@lazaroclapp lazaroclapp left a 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>[]>();",
Copy link
Collaborator

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?

Copy link
Collaborator

@msridhar msridhar Aug 29, 2023

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> {
Copy link
Collaborator

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).

Copy link
Collaborator

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[]
Copy link
Collaborator

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

See #818

Copy link
Collaborator

@lazaroclapp lazaroclapp left a comment

Choose a reason for hiding this comment

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

LGTM! 🚀

@msridhar msridhar merged commit 0431a90 into uber:master Aug 30, 2023
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Handle array types when checking JSpecify assignment compatibility
4 participants