-
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
Merged
Merged
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
a0b76ea
adding failing test case for issue 740
akulk022 b2c533a
adding visitors for handling ArrayTypes
akulk022 6ae742b
clean-up of redundant code in visitors
akulk022 15e1601
fixing indentation
akulk022 9fae10c
Merge branch 'master' into generic_type_issue_#740_ak
msridhar 47dd926
updating visitor methods returning null
akulk022 f5b6c1d
making default Type method Nullable in visitor
akulk022 e9d969b
formatting
msridhar 20d747a
changing TreeVisitor second argument type to Void since it is unused
akulk022 0d3c2f0
Update default return type for Compare Nullability Visitor
akulk022 4273601
removing cast from TYPE_ARG_VISITOR
akulk022 9e96103
removing unnecessary @Nullable annotation for method that always retu…
akulk022 d1e61bf
Merge branch 'generic_type_issue_#740_ak' of https://github.com/akulk…
akulk022 d2db3cc
Merge remote-tracking branch 'origin/master' into generic_type_issue_…
akulk022 d7c9812
After merging with changes made by PR#805 making GenericChecks method…
akulk022 aed09fd
Adding test cases
akulk022 1a12cef
Merge branch 'master' into generic_type_issue_#740_ak
msridhar a4bd9bc
Merge branch 'master' into generic_type_issue_#740_ak
msridhar 680a17c
Removing redundant code from TypeWithPreservedAnnotations
akulk022 d314952
Adding more descriptive comments for visitors and fixing formatting o…
akulk022 b9eb8fc
fix NPE issue
msridhar 989a755
fix other test failure
msridhar 4e5adf0
copy type symbol for array type from original
msridhar 657f38e
removing TYPE_ARG_VISITOR and the check to see if we have more nested…
akulk022 6556379
Update nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java
akulk022 ce51560
using the existing visitor instead of allocating a new one
akulk022 2e14f16
fix position of annotations
msridhar 6f5c722
add todo
msridhar b419103
restore
msridhar 394ff52
more tests
msridhar 1438bbd
Merge branch 'master' into generic_type_issue_#740_ak
msridhar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
import com.google.errorprone.util.ASTHelpers; | ||
import com.sun.source.tree.AnnotatedTypeTree; | ||
import com.sun.source.tree.AnnotationTree; | ||
import com.sun.source.tree.ArrayTypeTree; | ||
import com.sun.source.tree.AssignmentTree; | ||
import com.sun.source.tree.ConditionalExpressionTree; | ||
import com.sun.source.tree.ExpressionTree; | ||
|
@@ -20,6 +21,7 @@ | |
import com.sun.source.tree.ParameterizedTypeTree; | ||
import com.sun.source.tree.Tree; | ||
import com.sun.source.tree.VariableTree; | ||
import com.sun.source.util.SimpleTreeVisitor; | ||
import com.sun.tools.javac.code.Attribute; | ||
import com.sun.tools.javac.code.BoundKind; | ||
import com.sun.tools.javac.code.Symbol; | ||
|
@@ -354,57 +356,10 @@ public static void checkTypeParameterNullnessForFunctionReturnType( | |
* @param state the visitor state | ||
*/ | ||
private static boolean compareNullabilityAnnotations( | ||
Type.ClassType lhsType, Type.ClassType rhsType, VisitorState state) { | ||
Types types = state.getTypes(); | ||
// 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. | ||
rhsType = (Type.ClassType) types.asSuper(rhsType, lhsType.tsym); | ||
// This is impossible, considering the fact that standard Java subtyping succeeds before running | ||
// NullAway | ||
if (rhsType == null) { | ||
throw new RuntimeException("Did not find supertype of " + rhsType + " matching " + lhsType); | ||
} | ||
List<Type> lhsTypeArguments = lhsType.getTypeArguments(); | ||
List<Type> rhsTypeArguments = rhsType.getTypeArguments(); | ||
// This is impossible, considering the fact that standard Java subtyping succeeds before running | ||
// NullAway | ||
if (lhsTypeArguments.size() != rhsTypeArguments.size()) { | ||
throw new RuntimeException( | ||
"Number of types arguments in " + rhsType + " does not match " + lhsType); | ||
} | ||
for (int i = 0; i < lhsTypeArguments.size(); i++) { | ||
Type lhsTypeArgument = lhsTypeArguments.get(i); | ||
Type rhsTypeArgument = rhsTypeArguments.get(i); | ||
boolean isLHSNullableAnnotated = false; | ||
List<Attribute.TypeCompound> lhsAnnotations = lhsTypeArgument.getAnnotationMirrors(); | ||
// To ensure that we are checking only jspecify nullable annotations | ||
for (Attribute.TypeCompound annotation : lhsAnnotations) { | ||
if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { | ||
isLHSNullableAnnotated = true; | ||
break; | ||
} | ||
} | ||
boolean isRHSNullableAnnotated = false; | ||
List<Attribute.TypeCompound> rhsAnnotations = rhsTypeArgument.getAnnotationMirrors(); | ||
// To ensure that we are checking only jspecify nullable annotations | ||
for (Attribute.TypeCompound annotation : rhsAnnotations) { | ||
if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { | ||
isRHSNullableAnnotated = true; | ||
break; | ||
} | ||
} | ||
if (isLHSNullableAnnotated != isRHSNullableAnnotated) { | ||
return false; | ||
} | ||
// nested generics | ||
if (lhsTypeArgument.getTypeArguments().length() > 0) { | ||
if (!compareNullabilityAnnotations( | ||
(Type.ClassType) lhsTypeArgument, (Type.ClassType) rhsTypeArgument, state)) { | ||
return false; | ||
} | ||
} | ||
} | ||
return true; | ||
Type lhsType, Type rhsType, VisitorState state) { | ||
// it is fair to assume rhyType should be the same as lhsType as the Java compiler has passed | ||
// before NullAway. | ||
return lhsType.accept(new CompareNullabilityVisitor(state), rhsType); | ||
} | ||
|
||
/** | ||
|
@@ -418,56 +373,7 @@ private static boolean compareNullabilityAnnotations( | |
*/ | ||
private static Type.ClassType typeWithPreservedAnnotations( | ||
ParameterizedTypeTree tree, VisitorState state) { | ||
Type.ClassType type = (Type.ClassType) ASTHelpers.getType(tree); | ||
Preconditions.checkNotNull(type); | ||
Type nullableType = NULLABLE_TYPE_SUPPLIER.get(state); | ||
List<? extends Tree> typeArguments = tree.getTypeArguments(); | ||
List<Type> newTypeArgs = new ArrayList<>(); | ||
for (int i = 0; i < typeArguments.size(); i++) { | ||
AnnotatedTypeTree annotatedType = null; | ||
Tree curTypeArg = typeArguments.get(i); | ||
// If the type argument has an annotation, it will either be an AnnotatedTypeTree, or a | ||
// ParameterizedTypeTree in the case of a nested generic type | ||
if (curTypeArg instanceof AnnotatedTypeTree) { | ||
annotatedType = (AnnotatedTypeTree) curTypeArg; | ||
} else if (curTypeArg instanceof ParameterizedTypeTree | ||
&& ((ParameterizedTypeTree) curTypeArg).getType() instanceof AnnotatedTypeTree) { | ||
annotatedType = (AnnotatedTypeTree) ((ParameterizedTypeTree) curTypeArg).getType(); | ||
} | ||
List<? extends AnnotationTree> annotations = | ||
annotatedType != null ? annotatedType.getAnnotations() : Collections.emptyList(); | ||
boolean hasNullableAnnotation = false; | ||
for (AnnotationTree annotation : annotations) { | ||
if (ASTHelpers.isSameType( | ||
nullableType, ASTHelpers.getType(annotation.getAnnotationType()), state)) { | ||
hasNullableAnnotation = true; | ||
break; | ||
} | ||
} | ||
// construct a TypeMetadata object containing a nullability annotation if needed | ||
com.sun.tools.javac.util.List<Attribute.TypeCompound> nullableAnnotationCompound = | ||
hasNullableAnnotation | ||
? com.sun.tools.javac.util.List.from( | ||
Collections.singletonList( | ||
new Attribute.TypeCompound( | ||
nullableType, com.sun.tools.javac.util.List.nil(), null))) | ||
: com.sun.tools.javac.util.List.nil(); | ||
TypeMetadata typeMetadata = | ||
new TypeMetadata(new TypeMetadata.Annotations(nullableAnnotationCompound)); | ||
Type currentTypeArgType = castToNonNull(ASTHelpers.getType(curTypeArg)); | ||
if (currentTypeArgType.getTypeArguments().size() > 0) { | ||
// nested generic type; recursively preserve its nullability type argument annotations | ||
currentTypeArgType = | ||
typeWithPreservedAnnotations((ParameterizedTypeTree) curTypeArg, state); | ||
} | ||
Type.ClassType newTypeArgType = | ||
(Type.ClassType) currentTypeArgType.cloneWithMetadata(typeMetadata); | ||
newTypeArgs.add(newTypeArgType); | ||
} | ||
Type.ClassType finalType = | ||
new Type.ClassType( | ||
type.getEnclosingType(), com.sun.tools.javac.util.List.from(newTypeArgs), type.tsym); | ||
return finalType; | ||
return (Type.ClassType) tree.accept(new PreservedAnnotationTreeVisitor(state), null); | ||
} | ||
|
||
/** | ||
|
@@ -577,6 +483,156 @@ public static void compareGenericTypeParameterNullabilityForCall( | |
} | ||
} | ||
|
||
/** | ||
* Visitor that is called from compareNullabilityAnnotations which recursively compares the | ||
* 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> { | ||
private final VisitorState state; | ||
|
||
CompareNullabilityVisitor(VisitorState state) { | ||
this.state = state; | ||
} | ||
|
||
@Override | ||
public Boolean visitClassType(Type.ClassType lhsType, Type rhsType) { | ||
Types types = state.getTypes(); | ||
// 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. | ||
rhsType = (Type.ClassType) types.asSuper(rhsType, lhsType.tsym); | ||
// This is impossible, considering the fact that standard Java subtyping succeeds before | ||
// running NullAway | ||
if (rhsType == null) { | ||
throw new RuntimeException("Did not find supertype of " + rhsType + " matching " + lhsType); | ||
} | ||
List<Type> lhsTypeArguments = lhsType.getTypeArguments(); | ||
List<Type> rhsTypeArguments = rhsType.getTypeArguments(); | ||
// This is impossible, considering the fact that standard Java subtyping succeeds before | ||
// running NullAway | ||
if (lhsTypeArguments.size() != rhsTypeArguments.size()) { | ||
throw new RuntimeException( | ||
"Number of types arguments in " + rhsType + " does not match " + lhsType); | ||
} | ||
for (int i = 0; i < lhsTypeArguments.size(); i++) { | ||
Type lhsTypeArgument = lhsTypeArguments.get(i); | ||
Type rhsTypeArgument = rhsTypeArguments.get(i); | ||
boolean isLHSNullableAnnotated = false; | ||
List<Attribute.TypeCompound> lhsAnnotations = lhsTypeArgument.getAnnotationMirrors(); | ||
// To ensure that we are checking only jspecify nullable annotations | ||
for (Attribute.TypeCompound annotation : lhsAnnotations) { | ||
if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { | ||
isLHSNullableAnnotated = true; | ||
break; | ||
} | ||
} | ||
boolean isRHSNullableAnnotated = false; | ||
List<Attribute.TypeCompound> rhsAnnotations = rhsTypeArgument.getAnnotationMirrors(); | ||
// To ensure that we are checking only jspecify nullable annotations | ||
for (Attribute.TypeCompound annotation : rhsAnnotations) { | ||
if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { | ||
isRHSNullableAnnotated = true; | ||
break; | ||
} | ||
} | ||
if (isLHSNullableAnnotated != isRHSNullableAnnotated) { | ||
return false; | ||
} | ||
// nested generics | ||
if (!lhsTypeArgument.accept(this, rhsTypeArgument)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
@Override | ||
public Boolean visitArrayType(Type.ArrayType lhsType, Type rhsType) { | ||
Type.ArrayType arrRhsType = (Type.ArrayType) rhsType; | ||
return lhsType.getComponentType().accept(this, arrRhsType.getComponentType()); | ||
} | ||
|
||
@Override | ||
public Boolean visitType(Type t, Type type) { | ||
return true; | ||
} | ||
} | ||
|
||
/** | ||
* Visitor For getting the preserved Annotation Type for the nested generic type arguments within | ||
* the ParameterizedTypeTree originally passed to TypeWithPreservedAnnotations method, since these | ||
* nested arguments may not always be ParameterizedTypeTrees and may be of different types for | ||
* e.g. ArrayTypeTree. | ||
*/ | ||
public static class PreservedAnnotationTreeVisitor extends SimpleTreeVisitor<Type, Void> { | ||
|
||
private final VisitorState state; | ||
|
||
PreservedAnnotationTreeVisitor(VisitorState state) { | ||
this.state = state; | ||
} | ||
|
||
@Override | ||
public Type visitArrayType(ArrayTypeTree tree, Void p) { | ||
Type elemType = tree.getType().accept(this, null); | ||
return new Type.ArrayType(elemType, castToNonNull(ASTHelpers.getType(tree)).tsym); | ||
} | ||
|
||
@Override | ||
public Type visitParameterizedType(ParameterizedTypeTree tree, Void p) { | ||
Type.ClassType type = (Type.ClassType) ASTHelpers.getType(tree); | ||
msridhar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Preconditions.checkNotNull(type); | ||
Type nullableType = NULLABLE_TYPE_SUPPLIER.get(state); | ||
List<? extends Tree> typeArguments = tree.getTypeArguments(); | ||
List<Type> newTypeArgs = new ArrayList<>(); | ||
for (int i = 0; i < typeArguments.size(); i++) { | ||
AnnotatedTypeTree annotatedType = null; | ||
Tree curTypeArg = typeArguments.get(i); | ||
// If the type argument has an annotation, it will either be an AnnotatedTypeTree, or a | ||
// ParameterizedTypeTree in the case of a nested generic type | ||
if (curTypeArg instanceof AnnotatedTypeTree) { | ||
annotatedType = (AnnotatedTypeTree) curTypeArg; | ||
} else if (curTypeArg instanceof ParameterizedTypeTree | ||
&& ((ParameterizedTypeTree) curTypeArg).getType() instanceof AnnotatedTypeTree) { | ||
annotatedType = (AnnotatedTypeTree) ((ParameterizedTypeTree) curTypeArg).getType(); | ||
} | ||
List<? extends AnnotationTree> annotations = | ||
annotatedType != null ? annotatedType.getAnnotations() : Collections.emptyList(); | ||
boolean hasNullableAnnotation = false; | ||
for (AnnotationTree annotation : annotations) { | ||
if (ASTHelpers.isSameType( | ||
nullableType, ASTHelpers.getType(annotation.getAnnotationType()), state)) { | ||
hasNullableAnnotation = true; | ||
break; | ||
} | ||
} | ||
// construct a TypeMetadata object containing a nullability annotation if needed | ||
com.sun.tools.javac.util.List<Attribute.TypeCompound> nullableAnnotationCompound = | ||
hasNullableAnnotation | ||
? com.sun.tools.javac.util.List.from( | ||
Collections.singletonList( | ||
new Attribute.TypeCompound( | ||
nullableType, com.sun.tools.javac.util.List.nil(), null))) | ||
: com.sun.tools.javac.util.List.nil(); | ||
TypeMetadata typeMetadata = | ||
new TypeMetadata(new TypeMetadata.Annotations(nullableAnnotationCompound)); | ||
Type currentTypeArgType = curTypeArg.accept(this, null); | ||
Type newTypeArgType = currentTypeArgType.cloneWithMetadata(typeMetadata); | ||
newTypeArgs.add(newTypeArgType); | ||
} | ||
Type.ClassType finalType = | ||
new Type.ClassType( | ||
type.getEnclosingType(), com.sun.tools.javac.util.List.from(newTypeArgs), type.tsym); | ||
return finalType; | ||
} | ||
|
||
/** By default, just use the type computed by javac */ | ||
@Override | ||
protected Type defaultAction(Tree node, Void unused) { | ||
return castToNonNull(ASTHelpers.getType(node)); | ||
} | ||
} | ||
|
||
/** | ||
* Checks that type parameter nullability is consistent between an overriding method and the | ||
* corresponding overridden method. | ||
|
@@ -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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. See #818 |
||
return t.elemtype.accept(this, null) + "[]"; | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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