diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalCreator.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalCreator.java new file mode 100644 index 00000000000..c7344bdccc9 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalCreator.java @@ -0,0 +1,15 @@ +package org.checkerframework.checker.optional.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; + +/** An method annotation for methods that create an {@link java.util.Optional} */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@InheritedAnnotation +public @interface OptionalCreator {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalEliminator.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalEliminator.java new file mode 100644 index 00000000000..d1825468aa3 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalEliminator.java @@ -0,0 +1,15 @@ +package org.checkerframework.checker.optional.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; + +/** Methods whose receiver is an {@link java.util.Optional} and return a non-optional. */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@InheritedAnnotation +public @interface OptionalEliminator {} diff --git a/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalPropagator.java b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalPropagator.java new file mode 100644 index 00000000000..322a1c42045 --- /dev/null +++ b/checker-qual/src/main/java/org/checkerframework/checker/optional/qual/OptionalPropagator.java @@ -0,0 +1,15 @@ +package org.checkerframework.checker.optional.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.InheritedAnnotation; + +/** Methods whose receiver is an {@link java.util.Optional} and return an {@code Optional}. */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@InheritedAnnotation +public @interface OptionalPropagator {} 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 d909c8bd24d..120dbeadaf8 100644 --- a/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java +++ b/checker/src/main/java/org/checkerframework/checker/optional/OptionalVisitor.java @@ -15,7 +15,6 @@ 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.List; import javax.annotation.processing.ProcessingEnvironment; @@ -27,6 +26,9 @@ import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.optional.qual.OptionalCreator; +import org.checkerframework.checker.optional.qual.OptionalEliminator; +import org.checkerframework.checker.optional.qual.OptionalPropagator; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeValidator; @@ -51,15 +53,6 @@ public class OptionalVisitor /** The Collection type. */ private final TypeMirror collectionType; - /** The element for java.util.Optional.empty(). */ - private final ExecutableElement optionalEmpty; - - /** The element for java.util.Optional.filter(). */ - private final ExecutableElement optionalFilter; - - /** The element for java.util.Optional.flatMap(). */ - private final ExecutableElement optionalFlatMap; - /** The element for java.util.Optional.get(). */ private final ExecutableElement optionalGet; @@ -69,45 +62,12 @@ public class OptionalVisitor /** The element for java.util.Optional.isEmpty(), or null if running under JDK 8. */ private final @Nullable ExecutableElement optionalIsEmpty; - /** The element for java.util.Optional.map(). */ - private final ExecutableElement optionalMap; - - /** The element for java.util.Optional.of(). */ - private final ExecutableElement optionalOf; - - /** The element for java.util.Optional.ofNullable(). */ - private final ExecutableElement optionalOfNullable; - - /** The element for java.util.Optional.or(), or null if running under JDK 8. */ - private final @Nullable ExecutableElement optionalOr; - - /** The element for java.util.Optional.orElse(). */ - private final ExecutableElement optionalOrElse; - - /** The element for java.util.Optional.orElseGet(). */ - private final ExecutableElement optionalOrElseGet; - - /** The element for java.util.Optional.orElseThrow(), or null if running below Java 10. */ - private final @Nullable ExecutableElement optionalOrElseThrow; - - /** The element for java.util.Optional.orElseThrow(Supplier), or null if running under JDK 8. */ - private final @Nullable ExecutableElement optionalOrElseThrowSupplier; - /** The element for java.util.stream.Stream.filter(). */ private final ExecutableElement streamFilter; /** The element for java.util.stream.Stream.map(). */ private final ExecutableElement streamMap; - /** Static methods that create an Optional. */ - private final List optionalCreators; - - /** Methods whose receiver is an Optional, and return an Optional. */ - private final List optionalPropagators; - - /** Methods whose receiver is an Optional, and return a non-optional. */ - private final List optionalEliminators; - /** * Create an OptionalVisitor. * @@ -118,47 +78,12 @@ public OptionalVisitor(BaseTypeChecker checker) { collectionType = types.erasure(TypesUtils.typeFromClass(Collection.class, types, elements)); ProcessingEnvironment env = checker.getProcessingEnvironment(); - optionalEmpty = TreeUtils.getMethod("java.util.Optional", "empty", 0, env); - optionalFilter = TreeUtils.getMethod("java.util.Optional", "filter", 1, env); - optionalFlatMap = TreeUtils.getMethod("java.util.Optional", "flatMap", 1, env); optionalGet = TreeUtils.getMethod("java.util.Optional", "get", 0, env); optionalIsPresent = TreeUtils.getMethod("java.util.Optional", "isPresent", 0, env); optionalIsEmpty = TreeUtils.getMethodOrNull("java.util.Optional", "isEmpty", 0, env); - optionalMap = TreeUtils.getMethod("java.util.Optional", "map", 1, env); - optionalOf = TreeUtils.getMethod("java.util.Optional", "of", 1, env); - optionalOr = TreeUtils.getMethodOrNull("java.util.Optional", "or", 1, env); - optionalOfNullable = TreeUtils.getMethod("java.util.Optional", "ofNullable", 1, env); - optionalOrElse = TreeUtils.getMethod("java.util.Optional", "orElse", 1, env); - optionalOrElseGet = TreeUtils.getMethod("java.util.Optional", "orElseGet", 1, env); - optionalOrElseThrow = TreeUtils.getMethodOrNull("java.util.Optional", "orElseThrow", 0, env); - optionalOrElseThrowSupplier = TreeUtils.getMethod("java.util.Optional", "orElseThrow", 1, env); streamFilter = TreeUtils.getMethod("java.util.stream.Stream", "filter", 1, env); streamMap = TreeUtils.getMethod("java.util.stream.Stream", "map", 1, env); - - optionalCreators = Arrays.asList(optionalEmpty, optionalOf, optionalOfNullable); - optionalPropagators = - optionalOr == null - ? Arrays.asList(optionalFilter, optionalFlatMap, optionalMap) - : Arrays.asList(optionalFilter, optionalFlatMap, optionalMap, optionalOr); - // TODO: add these eliminators: - // hashCode, ifPresent, ifPresentOrElse (Java 9+ only), isEmpty, isPresent, toString, - // Object.getClass - optionalEliminators = - optionalIsEmpty == null - ? Arrays.asList( - optionalGet, - optionalOrElse, - optionalOrElseGet, - optionalOrElseThrow, - optionalOrElseThrowSupplier) - : Arrays.asList( - optionalGet, - optionalIsEmpty, - optionalOrElse, - optionalOrElseGet, - optionalOrElseThrow, - optionalOrElseThrowSupplier); } @Override @@ -223,8 +148,8 @@ private boolean isCallToGet(ExpressionTree expression) { * @return true iff the method being called is Optional creation: empty, of, ofNullable */ private boolean isOptionalCreation(MethodInvocationTree methInvok) { - return TreeUtils.isMethodInvocation( - methInvok, optionalCreators, checker.getProcessingEnvironment()); + ExecutableElement method = TreeUtils.elementFromUse(methInvok); + return atypeFactory.getDeclAnnotation(method, OptionalCreator.class) != null; } /** @@ -234,8 +159,8 @@ private boolean isOptionalCreation(MethodInvocationTree methInvok) { * @return true true iff the method being called is Optional propagation: filter, flatMap, map, or */ private boolean isOptionalPropagation(MethodInvocationTree methInvok) { - return TreeUtils.isMethodInvocation( - methInvok, optionalPropagators, checker.getProcessingEnvironment()); + ExecutableElement method = TreeUtils.elementFromUse(methInvok); + return atypeFactory.getDeclAnnotation(method, OptionalPropagator.class) != null; } /** @@ -247,8 +172,8 @@ private boolean isOptionalPropagation(MethodInvocationTree methInvok) { * orElseThrow */ private boolean isOptionalElimination(MethodInvocationTree methInvok) { - return TreeUtils.isMethodInvocation( - methInvok, optionalEliminators, checker.getProcessingEnvironment()); + ExecutableElement method = TreeUtils.elementFromUse(methInvok); + return atypeFactory.getDeclAnnotation(method, OptionalEliminator.class) != null; } @Override