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

VoidMissingNullable: handle implicit parameterized types #4123

Conversation

Stephan202
Copy link
Contributor

While there, simplify a few other places where implicit AST nodes are
identified.

While there, simplify a few other places where implicit AST nodes are
identified.
Copy link
Contributor Author

@Stephan202 Stephan202 left a 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)))
Copy link
Contributor Author

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?

Copy link
Collaborator

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

Copy link
Contributor Author

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.

Comment on lines +90 to +93
if (!hasExplicitSource(parameterizedTypeTree, state)) {
/* Implicit types cannot be annotated. */
return NO_MATCH;
}
Copy link
Contributor Author

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.

Comment on lines 297 to 309
@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();
}
Copy link
Contributor Author

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)

Copy link
Collaborator

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

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.

Copy link
Contributor Author

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

Copy link
Collaborator

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

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

Copy link
Contributor Author

@Stephan202 Stephan202 left a 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)))
Copy link
Contributor Author

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);
Copy link
Contributor Author

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

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

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

copybara-service bot pushed a commit that referenced this pull request Sep 29, 2023
While there, simplify a few other places where implicit AST nodes are
identified.

Fixes #4123

FUTURE_COPYBARA_INTEGRATE_REVIEW=#4123 from PicnicSupermarket:sschroevers/fix-VoidMissingNullable 3741524
PiperOrigin-RevId: 569503189
@Stephan202 Stephan202 deleted the sschroevers/fix-VoidMissingNullable branch September 29, 2023 17:46
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.

2 participants