-
Notifications
You must be signed in to change notification settings - Fork 756
VoidMissingNullable: handle implicit parameterized types #4123
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
VoidMissingNullable: handle implicit parameterized types #4123
Conversation
While there, simplify a few other places where implicit AST nodes are identified.
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'm open to alternative implementations. Also, let me know if I should extract the cleanup logic to a separate PR.
CC @cpovirk as the author of VoidMissingNullable
.
@@ -1117,7 +1118,7 @@ private static Tree suppressibleNode(TreePath path) { | |||
&& ((ClassTree) tree).getSimpleName().length() != 0) | |||
// Lambda parameters can't be suppressed unless they have Type decls | |||
|| (tree instanceof VariableTree | |||
&& getStartPosition(((VariableTree) tree).getType()) != -1)) | |||
&& !hasNoExplicitType((VariableTree) tree, 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.
This shows that hasNoExplicitType
should perhaps be renamed to hasExplicitType
, with negated semantics. WDYT?
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 the double-negation is unfortunate, either hasExplicitType
with negated semantics, or hasImplicitType
with the same semantics seem like an improvement. If you want to make one of those changes here I'm happy to approve it :)
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.
Cool! Perhaps hasImplicitType
better describes the "edge case" that developers looking for this method are interested in; will use that.
if (!hasExplicitSource(parameterizedTypeTree, state)) { | ||
/* Implicit types cannot be annotated. */ | ||
return NO_MATCH; | ||
} |
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 could expand the comment like here, but I'm not sure that we want to make a similarly strong statement about inference of local parameterized types.
@Test | ||
public void negativeGenericLambdaParameterNoType() { | ||
aggressiveCompilationHelper | ||
.addSourceLines( | ||
"Test.java", | ||
"import org.jspecify.annotations.Nullable;", | ||
"interface Test {", | ||
" void consume(Iterable<@Nullable Void> it);", | ||
"", | ||
" Test TEST = it -> {};", | ||
"}") | ||
.doTest(); | ||
} |
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.
Without these changes the test fails with:
java.lang.AssertionError: An unhandled exception was thrown by the Error Prone static analysis plugin.
Please report this at https://github.com/google/error-prone/issues/new and include the following:
error-prone version: unknown version
BugPattern: VoidMissingNullable
Stack Trace:
java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:143)
at com.google.errorprone.bugpatterns.nullness.NullnessUtils.fixByAddingKnownTypeUseNullableAnnotation(NullnessUtils.java:235)
at com.google.errorprone.bugpatterns.nullness.NullnessUtils.fixByAnnotatingTypeUseOnlyLocationWithNullableAnnotation(NullnessUtils.java:200)
at com.google.errorprone.bugpatterns.nullness.VoidMissingNullable.checkTree(VoidMissingNullable.java:191)
at com.google.errorprone.bugpatterns.nullness.VoidMissingNullable.matchParameterizedType(VoidMissingNullable.java:107)
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 good overall, thanks!
* Note that the .isImplicitlyTyped() method on JCVariableDecl returns the wrong answer after | ||
* type attribution has occurred. | ||
*/ | ||
return getStartPosition(tree.getType()) == -1; | ||
return !hasExplicitSource(tree.getType(), 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.
We used to check getEndPosition
in hasNoExplicitType
to work around a javac8 bug, and the workaround was removed in 1b8bf17. I can't remember if there was any motivation for that beyond removing references to javac8 bugs after we dropped JDK 8 support.
I also remembered that recent versions of JCVariableDecl
(including in JDK 11u) have a declaredUsingVar
getter that I think we could be using here. We might also need to check isImplicitlyTyped
.
Thoughts on using that approach instead of delegating? I think it reads as well and avoids having to check end positions.
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.
Though the unconditional downcast casts always make me a little uncomfortable, there's precedent even in this class to unconditionally cast to JCVariableDecl
. So I tried this:
public static boolean hasImplicitType(VariableTree tree, VisitorState state) {
JCVariableDecl varTree = (JCVariableDecl) tree;
return varTree.declaredUsingVar() || varTree.isImplicitlyTyped();
}
Unfortunately this causes the existing negativeLambdaParameterNoType
test to fail:
java.lang.AssertionError: Saw unexpected error on line 5. All errors:
/Test.java:5: Note: [VoidMissingNullable] The type Void is not annotated @Nullable
Test TEST = v -> {};
^
(see https://errorprone.info/bugpattern/VoidMissingNullable)
Did you mean 'Test TEST = @Nullable v -> {};'?
at org.junit.Assert.fail(Assert.java:89)
at com.google.errorprone.DiagnosticTestHelper.assertHasDiagnosticOnAllMatchingLines(DiagnosticTestHelper.java:348)
at com.google.errorprone.CompilationTestHelper.doTest(CompilationTestHelper.java:342)
at com.google.errorprone.bugpatterns.nullness.VoidMissingNullableTest.negativeLambdaParameterNoType(VoidMissingNullableTest.java:294)
...
When I attach a debugger then I see that indeed declaredUsingVar
is false, and vartype
is a JCIdent
representing Void
. I'm guessing (but did not investigate deeper) that this has something to do with the lambda expression being assigned the type of the targeted functional interface.
I'll propose a new comment to document this observation.
(Tested with Temurin-11.0.20.1+1 and Temurin-17.0.8.1+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.
Thanks for trying that out, and for the comment.
@@ -1117,7 +1118,7 @@ private static Tree suppressibleNode(TreePath path) { | |||
&& ((ClassTree) tree).getSimpleName().length() != 0) | |||
// Lambda parameters can't be suppressed unless they have Type decls | |||
|| (tree instanceof VariableTree | |||
&& getStartPosition(((VariableTree) tree).getType()) != -1)) | |||
&& !hasNoExplicitType((VariableTree) tree, 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.
I agree the double-negation is unfortunate, either hasExplicitType
with negated semantics, or hasImplicitType
with the same semantics seem like an improvement. If you want to make one of those changes here I'm happy to approve it :)
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.
Added a commit; also fixed some formatting.
@@ -1117,7 +1118,7 @@ private static Tree suppressibleNode(TreePath path) { | |||
&& ((ClassTree) tree).getSimpleName().length() != 0) | |||
// Lambda parameters can't be suppressed unless they have Type decls | |||
|| (tree instanceof VariableTree | |||
&& getStartPosition(((VariableTree) tree).getType()) != -1)) | |||
&& !hasNoExplicitType((VariableTree) tree, 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.
Cool! Perhaps hasImplicitType
better describes the "edge case" that developers looking for this method are interested in; will use that.
* Note that the .isImplicitlyTyped() method on JCVariableDecl returns the wrong answer after | ||
* type attribution has occurred. | ||
*/ | ||
return getStartPosition(tree.getType()) == -1; | ||
return !hasExplicitSource(tree.getType(), 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.
Though the unconditional downcast casts always make me a little uncomfortable, there's precedent even in this class to unconditionally cast to JCVariableDecl
. So I tried this:
public static boolean hasImplicitType(VariableTree tree, VisitorState state) {
JCVariableDecl varTree = (JCVariableDecl) tree;
return varTree.declaredUsingVar() || varTree.isImplicitlyTyped();
}
Unfortunately this causes the existing negativeLambdaParameterNoType
test to fail:
java.lang.AssertionError: Saw unexpected error on line 5. All errors:
/Test.java:5: Note: [VoidMissingNullable] The type Void is not annotated @Nullable
Test TEST = v -> {};
^
(see https://errorprone.info/bugpattern/VoidMissingNullable)
Did you mean 'Test TEST = @Nullable v -> {};'?
at org.junit.Assert.fail(Assert.java:89)
at com.google.errorprone.DiagnosticTestHelper.assertHasDiagnosticOnAllMatchingLines(DiagnosticTestHelper.java:348)
at com.google.errorprone.CompilationTestHelper.doTest(CompilationTestHelper.java:342)
at com.google.errorprone.bugpatterns.nullness.VoidMissingNullableTest.negativeLambdaParameterNoType(VoidMissingNullableTest.java:294)
...
When I attach a debugger then I see that indeed declaredUsingVar
is false, and vartype
is a JCIdent
representing Void
. I'm guessing (but did not investigate deeper) that this has something to do with the lambda expression being assigned the type of the targeted functional interface.
I'll propose a new comment to document this observation.
(Tested with Temurin-11.0.20.1+1 and Temurin-17.0.8.1+1.)
* Note that the .isImplicitlyTyped() method on JCVariableDecl returns the wrong answer after | ||
* type attribution has occurred. | ||
*/ | ||
return getStartPosition(tree.getType()) == -1; | ||
return !hasExplicitSource(tree.getType(), 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.
Thanks for trying that out, and for the comment.
@@ -2645,15 +2646,18 @@ private void scan(Type from, Type to) { | |||
} | |||
|
|||
/** Returns {@code true} if this is a `var` or a lambda parameter that has no explicit type. */ | |||
public static boolean hasNoExplicitType(VariableTree tree, VisitorState 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.
I'm going to temporarily add hasNoExplicitType
back, but make it deprecated and have it delegate to hasImplicitType
. We have some internal checks calling the old method and it's easier than fixing everything atomically, and it might also be helpful to other users to leave the old method around for a release or two.
(I have started importing the change and can make that fix on my side, you don't need to update the PR.)
While there, simplify a few other places where implicit AST nodes are
identified.