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

Custom path to Optional class for Optional emptiness handler #288

Merged
merged 5 commits into from Mar 22, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java
Expand Up @@ -70,7 +70,7 @@ public abstract class AbstractConfig implements Config {

protected boolean checkOptionalEmptiness;

protected String optionalClassPath;
protected Set<String> optionalClassPaths;

protected boolean assertsEnabled;

Expand Down Expand Up @@ -192,8 +192,8 @@ public boolean checkOptionalEmptiness() {
}

@Override
public String getOptionalClassPath() {
return optionalClassPath;
public Set<String> getOptionalClassPaths() {
return optionalClassPaths;
}

@Override
Expand Down
7 changes: 4 additions & 3 deletions nullaway/src/main/java/com/uber/nullaway/Config.java
Expand Up @@ -24,6 +24,7 @@

import com.google.common.collect.ImmutableSet;
import com.sun.tools.javac.code.Symbol;
import java.util.Set;
import javax.annotation.Nullable;

/** Provides configuration parameters for the nullability checker. */
Expand Down Expand Up @@ -116,10 +117,10 @@ public interface Config {
boolean checkOptionalEmptiness();

/**
* @return the path for Optional class. In default case the path of {@link java.util.Optional} is
* used.
* @return the paths for Optional class. The list always contains the path of {@link
* java.util.Optional}.
*/
String getOptionalClassPath();
Set<String> getOptionalClassPaths();

/**
* @return the fully qualified name of a method which will take a @Nullable version of a value and
Expand Down
Expand Up @@ -27,6 +27,7 @@

import com.google.common.collect.ImmutableSet;
import com.sun.tools.javac.code.Symbol;
import java.util.Set;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -118,7 +119,7 @@ public boolean checkOptionalEmptiness() {
}

@Override
public String getOptionalClassPath() {
public Set<String> getOptionalClassPaths() {
throw new IllegalStateException(error_msg);
}

Expand Down
Expand Up @@ -56,7 +56,7 @@ final class ErrorProneCLIFlagsConfig extends AbstractConfig {
static final String FL_ACKNOWLEDGE_RESTRICTIVE =
EP_FL_NAMESPACE + ":AcknowledgeRestrictiveAnnotations";
static final String FL_CHECK_OPTIONAL_EMPTINESS = EP_FL_NAMESPACE + ":CheckOptionalEmptiness";
static final String FL_OPTIONAL_CLASS_PACKAGE = EP_FL_NAMESPACE + ":OptionalClassPath";
static final String FL_OPTIONAL_CLASS_PATHS = EP_FL_NAMESPACE + ":OptionalClassPaths";
Copy link
Collaborator

Choose a reason for hiding this comment

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

I still think this should be CustomOptionalClasses, otherwise I keep parsing it on my head as "Optional (class paths)" rather than "(Optional class) paths"

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not that my alternative is perfect, but... mmm... maybe CheckOptionalEmptinessCustomClasses? A bit verbose, but at least ties the two options together.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed it to CheckOptionalEmptinessCustomClasses, sounds better

static final String FL_SUPPRESS_COMMENT = EP_FL_NAMESPACE + ":AutoFixSuppressionComment";
/** --- JarInfer configs --- */
static final String FL_JI_ENABLED = EP_FL_NAMESPACE + ":JarInferEnabled";
Expand Down Expand Up @@ -142,7 +142,11 @@ final class ErrorProneCLIFlagsConfig extends AbstractConfig {
getFlagStringSet(flags, FL_EXCLUDED_FIELD_ANNOT, DEFAULT_EXCLUDED_FIELD_ANNOT));
castToNonNullMethod = flags.get(FL_CTNN_METHOD).orElse(null);
autofixSuppressionComment = flags.get(FL_SUPPRESS_COMMENT).orElse("");
optionalClassPath = flags.get(FL_OPTIONAL_CLASS_PACKAGE).orElse("java.util.Optional");
optionalClassPaths =
new ImmutableSet.Builder<String>()
.addAll(getFlagStringSet(flags, FL_OPTIONAL_CLASS_PATHS))
.add("java.util.Optional")
.build();
if (autofixSuppressionComment.contains("\n")) {
throw new IllegalStateException(
"Invalid -XepOpt" + FL_SUPPRESS_COMMENT + " value. Comment must be single line.");
Expand Down
Expand Up @@ -21,7 +21,6 @@
*/
package com.uber.nullaway.handlers;

import com.google.common.base.Preconditions;
import com.google.errorprone.VisitorState;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
Expand All @@ -38,6 +37,8 @@
import com.uber.nullaway.dataflow.AccessPath;
import com.uber.nullaway.dataflow.AccessPathNullnessPropagation;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
Expand All @@ -50,7 +51,7 @@
*/
public class OptionalEmptinessHandler extends BaseNoOpHandler {

@Nullable private Optional<Type> optionalType;
@Nullable private Set<Optional<Type>> optionalTypes;
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 this a Set<Optional<Type>> rather than a Set<Type>? We could just filter out any type paths for which state.getTypeFromString(type) returns null.

Also, it probably should be an ImmutableSet, since it won't change after the call to onMatchTopLevelClass

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done 👍


@Override
public boolean onOverrideMayBeNullExpr(
Expand All @@ -69,11 +70,15 @@ public void onMatchTopLevelClass(
VisitorState state,
Symbol.ClassSymbol classSymbol,
Config config) {
if (optionalType == null) {
optionalType =
Optional.ofNullable(state.getTypeFromString(config.getOptionalClassPath()))
.map(state.getTypes()::erasure);
}
optionalTypes =
config
.getOptionalClassPaths()
.stream()
.map(
type ->
Optional.ofNullable(state.getTypeFromString(type))
.map(state.getTypes()::erasure))
.collect(Collectors.toSet());
}

@Override
Expand Down Expand Up @@ -144,20 +149,22 @@ private void updateNonNullAPsForElement(
}

private boolean optionalIsPresentCall(Symbol.MethodSymbol symbol, Types types) {
Preconditions.checkNotNull(optionalType);
// noinspection ConstantConditions
return optionalType.isPresent()
&& symbol.getSimpleName().toString().equals("isPresent")
&& symbol.getParameters().length() == 0
&& types.isSubtype(symbol.owner.type, optionalType.get());
for (Optional<Type> optionalType : optionalTypes) {
if (optionalType.isPresent()
&& symbol.getSimpleName().toString().equals("isPresent")
&& symbol.getParameters().length() == 0
&& types.isSubtype(symbol.owner.type, optionalType.get())) return true;
}
return false;
}

private boolean optionalIsGetCall(Symbol.MethodSymbol symbol, Types types) {
Preconditions.checkNotNull(optionalType);
// noinspection ConstantConditions
return optionalType.isPresent()
&& symbol.getSimpleName().toString().equals("get")
&& symbol.getParameters().length() == 0
&& types.isSubtype(symbol.owner.type, optionalType.get());
for (Optional<Type> optionalType : optionalTypes) {
if (optionalType.isPresent()
&& symbol.getSimpleName().toString().equals("get")
&& symbol.getParameters().length() == 0
&& types.isSubtype(symbol.owner.type, optionalType.get())) return true;
}
return false;
}
}
27 changes: 24 additions & 3 deletions nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java
Expand Up @@ -1234,7 +1234,7 @@ public void OptionalEmptinessHandlerTest() {
}

@Test
public void OptionalEmptinessHandlerWithCustomPathTest() {
public void OptionalEmptinessHandlerWithSingleCustomPathTest() {
compilationHelper
.setArgs(
Arrays.asList(
Expand All @@ -1243,7 +1243,7 @@ public void OptionalEmptinessHandlerWithCustomPathTest() {
"-XepOpt:NullAway:AnnotatedPackages=com.uber",
"-XepOpt:NullAway:UnannotatedSubPackages=com.uber.lib.unannotated",
"-XepOpt:NullAway:CheckOptionalEmptiness=true",
"-XepOpt:NullAway:OptionalClassPath=com.google.common.base.Optional"))
"-XepOpt:NullAway:OptionalClassPaths=com.google.common.base.Optional,does.not.matter.Optional"))
.addSourceLines(
"TestNegative.java",
"package com.uber;",
Expand Down Expand Up @@ -1271,11 +1271,32 @@ public void OptionalEmptinessHandlerWithCustomPathTest() {
.addSourceLines(
"TestPositive.java",
"package com.uber;",
"import com.google.common.base.Optional;",
"import java.util.Optional;",
"import javax.annotation.Nullable;",
"import com.google.common.base.Function;",
"public class TestPositive {",
" void foo() {",
" Optional<Object> a = Optional.empty();",
" // BUG: Diagnostic contains: Optional a can be empty",
" a.get().toString();",
" }",
" public void lambdaConsumer(Function a){",
" return;",
" }",
" void bar() {",
" Optional<Object> b = Optional.empty();",
" // BUG: Diagnostic contains: Optional b can be empty",
" lambdaConsumer(v -> b.get().toString());",
" }",
"}")
.addSourceLines(
"TestPositive2.java",
"package com.uber;",
"import com.google.common.base.Optional;",
"import javax.annotation.Nullable;",
"import com.google.common.base.Function;",
"public class TestPositive2 {",
" void foo() {",
" Optional<Object> a = Optional.absent();",
" // BUG: Diagnostic contains: Optional a can be empty",
" a.get().toString();",
Expand Down