From ae8a6e1d1ec113b25667e9a1ac13ce020dc12fe8 Mon Sep 17 00:00:00 2001 From: Suzanne Millstein Date: Tue, 12 Dec 2023 20:51:42 -0800 Subject: [PATCH] Add support for OptionalDouble, OptionalInt, OptionalLong Co-authored-by: Michael Ernst --- .../checker/optional/OptionalVisitor.java | 32 +++++++++++++++++-- checker/tests/optional/Marks4.java | 10 ++++++ checker/tests/optional/OptionalBoxed.java | 24 ++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 checker/tests/optional/OptionalBoxed.java diff --git a/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java b/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java index 120dbeadaf8..b9c3f38170b 100644 --- a/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java @@ -15,8 +15,11 @@ import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; +import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -401,6 +404,10 @@ public void handleCreationElimination(MethodInvocationTree tree) { } ExpressionTree receiver = TreeUtils.getReceiverTree(tree); while (true) { + if (receiver == null) { + // The receiver can be null if the receiver is the implicit "this.". + return; + } if (receiver.getKind() != Tree.Kind.METHOD_INVOCATION) { return; } @@ -546,14 +553,33 @@ public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) { } } - /** Return true if tm represents a subtype of Collection (other than the Null type). */ + /** + * Return true if tm is a subtype of Collection (other than the Null type). + * + * @param tm a type + * @return true if the given type is a subtype of Collection + */ private boolean isCollectionType(TypeMirror tm) { return tm.getKind() == TypeKind.DECLARED && types.isSubtype(tm, collectionType); } - /** Return true if tm represents java.util.Optional. */ + /** The fully-qualified names of the 4 optional classes in java.util. */ + private static final Set fqOptionalTypes = + new HashSet<>( + Arrays.asList( + "java.util.Optional", + "java.util.OptionalDouble", + "java.util.OptionalInt", + "java.util.OptionalLong")); + + /** + * Return true if tm is class Optional, OptionalDouble, OptionalInt, or OptionalLong in java.util. + * + * @param tm a type + * @return true if the given type is Optional, OptionalDouble, OptionalInt, or OptionalLong + */ private boolean isOptionalType(TypeMirror tm) { - return TypesUtils.isDeclaredOfName(tm, "java.util.Optional"); + return TypesUtils.isDeclaredOfName(tm, fqOptionalTypes); } /** diff --git a/checker/tests/optional/Marks4.java b/checker/tests/optional/Marks4.java index b89e0eb02ba..87f6eabe094 100644 --- a/checker/tests/optional/Marks4.java +++ b/checker/tests/optional/Marks4.java @@ -16,6 +16,16 @@ String process_bad(String s) { return Optional.ofNullable(s).orElseGet(this::getDefault); } + String process_bad2(String s) { + // :: warning: (introduce.eliminate) + return Optional.empty().orElseGet(this::getDefault); + } + + String process_bad3(String s) { + // :: warning: (introduce.eliminate) + return Optional.of(s).orElseGet(this::getDefault); + } + String process_good(String s) { return (s != null) ? s : getDefault(); } diff --git a/checker/tests/optional/OptionalBoxed.java b/checker/tests/optional/OptionalBoxed.java new file mode 100644 index 00000000000..766d10ff428 --- /dev/null +++ b/checker/tests/optional/OptionalBoxed.java @@ -0,0 +1,24 @@ +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +class OptionalBoxed { + + // :: warning: (optional.field) + OptionalDouble aField; + + // :: warning: (optional.parameter) + void m(OptionalInt aParam) { + // :: warning: (introduce.eliminate) + int x = OptionalLong.of(1L).hashCode(); + // :: warning: (introduce.eliminate) + long y = OptionalLong.of(1L).orElse(2L); + // :: warning: (introduce.eliminate) + boolean b = Optional.empty().isPresent(); + // :: warning: (introduce.eliminate) + OptionalDouble.empty().ifPresent(d -> {}); + // :: warning: (introduce.eliminate) + boolean b4 = OptionalLong.empty().isPresent(); + } +}