diff --git a/build.gradle b/build.gradle
index 1e430322c0c..3cde7a6a1fe 100644
--- a/build.gradle
+++ b/build.gradle
@@ -114,7 +114,7 @@ allprojects { currentProj ->
// * any new checkers have been added, or
// * backward-incompatible changes have been made to APIs or elsewhere.
// To make a snapshot release: ./gradlew publish
- version '3.44.1-SNAPSHOT'
+ version '3.44.2-SNAPSHOT'
tasks.withType(JavaCompile).configureEach {
options.fork = true
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmpty.java
new file mode 100644
index 00000000000..d997df2fc83
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmpty.java
@@ -0,0 +1,53 @@
+package org.checkerframework.checker.nonempty.qual;
+
+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;
+import org.checkerframework.framework.qual.PostconditionAnnotation;
+
+/**
+ * Indicates that a particular expression evaluates to a non-empty value, if the method terminates
+ * successfully.
+ *
+ *
This annotation applies to {@link java.util.Collection}, {@link java.util.Iterator}, {@link
+ * java.lang.Iterable}, and {@link java.util.Map}, but not {@link java.util.Optional}.
+ *
+ *
This postcondition annotation is useful for methods that make a value non-empty by side
+ * effect:
+ *
+ *
+ *
+ * It can also be used for a method that fails if a given value is empty, indicating that the
+ * argument is non-empty if the method returns normally:
+ *
+ *
+ * /** Throws an exception if the argument is empty. */
+ * {@literal @}EnsuresNonEmpty("#1")
+ * void useTheMap(Map<T, U> arg) { ... }
+ *
+ *
+ * @see NonEmpty
+ * @see org.checkerframework.checker.nonempty.NonEmptyChecker
+ * @checker_framework.manual #non-empty-checker Non-Empty Checker
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@PostconditionAnnotation(qualifier = NonEmpty.class)
+@InheritedAnnotation
+public @interface EnsuresNonEmpty {
+ /**
+ * The expression (a collection, iterator, iterable, or map) that is non-empty, if the method
+ * returns normally.
+ *
+ * @return the expression (a collection, iterator, iterable, or map) that is non-empty, if the
+ * method returns normally
+ */
+ String[] value();
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmptyIf.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmptyIf.java
new file mode 100644
index 00000000000..b5af54e90cd
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/EnsuresNonEmptyIf.java
@@ -0,0 +1,87 @@
+package org.checkerframework.checker.nonempty.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.ConditionalPostconditionAnnotation;
+import org.checkerframework.framework.qual.InheritedAnnotation;
+
+/**
+ * Indicates that the specific expressions are non-empty, if the method returns the given result
+ * (either true or false).
+ *
+ *
Here are ways this conditional postcondition annotation can be used:
+ *
+ *
Method parameters: Suppose that a method has a parameter that is a list, and returns
+ * true if the length of the list is non-zero. You could annotate the method as follows:
+ *
+ *
+ *
+ * because, if {@code isLengthGreaterThanZero} returns true, then {@code items} was non-empty. Note
+ * that you can write more than one {@code @EnsuresNonEmptyIf} annotations on a single method.
+ *
+ *
Fields: The value expression can refer to fields, even private ones. For example:
+ *
+ *
+ *
+ * An {@code @EnsuresNonEmptyIf} annotation that refers to a private field is useful for verifying
+ * that a method establishes a property, even though client code cannot directly affect the field.
+ *
+ *
Method postconditions: Suppose that if a method {@code areOrdersActive()} returns true,
+ * then {@code getOrders()} will return a non-empty Map. You can express this relationship as:
+ *
+ *
+ *
+ * @see NonEmpty
+ * @see EnsuresNonEmpty
+ * @checker_framework.manual #non-empty-checker Non-Empty Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+@ConditionalPostconditionAnnotation(qualifier = NonEmpty.class)
+@InheritedAnnotation
+public @interface EnsuresNonEmptyIf {
+
+ /**
+ * A return value of the method; when the method returns that value, the postcondition holds.
+ *
+ * @return the return value of the method for which the postcondition holds
+ */
+ boolean result();
+
+ /**
+ * Returns the Java expressions that are non-empty after the method returns the given result.
+ *
+ * @return the Java expressions that are non-empty after the method returns the given result
+ */
+ String[] expression();
+
+ /**
+ * A wrapper annotation that makes the {@link EnsuresNonEmptyIf} annotation repeatable.
+ *
+ *
Programmers generally do not need to write ths. It is created by Java when a programmer
+ * writes more than one {@link EnsuresNonEmptyIf} annotation at the same location.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+ @ConditionalPostconditionAnnotation(qualifier = NonEmpty.class)
+ @interface List {
+ /**
+ * Returns the repeatable annotations.
+ *
+ * @return the repeatable annotations
+ */
+ EnsuresNonEmptyIf[] value();
+ }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/NonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/NonEmpty.java
new file mode 100644
index 00000000000..d5a7f8c15d5
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/NonEmpty.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.nonempty.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.SubtypeOf;
+
+/**
+ * The {@link java.util.Collection Collection}, {@link java.util.Iterator Iterator}, {@link
+ * java.lang.Iterable Iterable}, or {@link java.util.Map Map} is definitely non-empty.
+ *
+ * @checker_framework.manual #non-empty-checker Non-Empty Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@SubtypeOf(UnknownNonEmpty.class)
+public @interface NonEmpty {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/PolyNonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/PolyNonEmpty.java
new file mode 100644
index 00000000000..dd538314595
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/PolyNonEmpty.java
@@ -0,0 +1,20 @@
+package org.checkerframework.checker.nonempty.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.PolymorphicQualifier;
+
+/**
+ * A polymorphic qualifier for the Non-Empty type system.
+ *
+ * @checker_framework.manual #non-empty-checker Non-Empty Checker
+ * @checker_framework.manual #qualifier-polymorphism Qualifier polymorphism
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
+@PolymorphicQualifier(UnknownNonEmpty.class)
+public @interface PolyNonEmpty {}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/RequiresNonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/RequiresNonEmpty.java
new file mode 100644
index 00000000000..45b46d4233d
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/RequiresNonEmpty.java
@@ -0,0 +1,96 @@
+package org.checkerframework.checker.nonempty.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.PreconditionAnnotation;
+
+/**
+ * Indicates a method precondition: the specified expressions that may be a {@link
+ * java.util.Collection collection}, {@link java.util.Iterator iterator}, {@link java.lang.Iterable
+ * iterable}, or {@link java.util.Map map} must be non-empty when the annotated method is invoked.
+ *
+ *
+ *
+ * This annotation should not be used for formal parameters (instead, give them a {@code @NonEmpty}
+ * type). The {@code @RequiresNonEmpty} annotation is intended for non-parameter expressions, such
+ * as field accesses or method calls.
+ *
+ * @checker_framework.manual #non-empty-checker Non-Empty Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.PARAMETER})
+@PreconditionAnnotation(qualifier = NonEmpty.class)
+public @interface RequiresNonEmpty {
+
+ /**
+ * The Java {@link java.util.Collection collection}, {@link java.util.Iterator iterator}, {@link
+ * java.lang.Iterable iterable}, or {@link java.util.Map map} that must be non-empty.
+ *
+ * @return the Java expression that must be non-empty
+ */
+ String[] value();
+
+ /**
+ * A wrapper annotation that makes the {@link RequiresNonEmpty} annotation repeatable.
+ *
+ *
Programmers generally do not need to write this. It is created by Java when a programmer
+ * writes more than one {@link RequiresNonEmpty} annotation at the same location.
+ */
+ @interface List {
+ /**
+ * Returns the repeatable annotations.
+ *
+ * @return the repeatable annotations
+ */
+ RequiresNonEmpty[] value();
+ }
+}
diff --git a/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/UnknownNonEmpty.java b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/UnknownNonEmpty.java
new file mode 100644
index 00000000000..65900f7fa12
--- /dev/null
+++ b/checker-qual/src/main/java/org/checkerframework/checker/nonempty/qual/UnknownNonEmpty.java
@@ -0,0 +1,22 @@
+package org.checkerframework.checker.nonempty.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.DefaultQualifierInHierarchy;
+import org.checkerframework.framework.qual.SubtypeOf;
+
+/**
+ * The {@link java.util.Collection Collection}, {@link java.util.Iterator Iterator}, {@link
+ * java.lang.Iterable Iterable}, or {@link java.util.Map Map} may or may not be empty.
+ *
+ * @checker_framework.manual #non-empty-checker Non-Empty Checker
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+@DefaultQualifierInHierarchy
+@SubtypeOf({})
+public @interface UnknownNonEmpty {}
diff --git a/checker/bin/wpi-many.sh b/checker/bin/wpi-many.sh
index 1010a156a51..3c0644f57e0 100755
--- a/checker/bin/wpi-many.sh
+++ b/checker/bin/wpi-many.sh
@@ -159,7 +159,7 @@ if [ "${has_java8}" = "no" ] && [ "${has_java11}" = "no" ] && [ "${has_java17}"
fi
if [ "${CHECKERFRAMEWORK}" = "" ]; then
- echo "CHECKERFRAMEWORK is not set; it must be set to a locally-built Checker Framework. Please clone and build github.com/typetools/checker-framework"
+ echo "CHECKERFRAMEWORK is not set; it must be set to a locally-built Checker Framework. Please clone and build https://github.com/typetools/checker-framework"
exit 2
fi
diff --git a/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java
index 0a8bacac849..d1b19c4d02a 100644
--- a/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java
+++ b/checker/src/main/java/org/checkerframework/checker/interning/InterningAnnotatedTypeFactory.java
@@ -78,7 +78,7 @@ public class InterningAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
final AnnotationMirrorSet INTERNED_SET = AnnotationMirrorSet.singleton(INTERNED);
/**
- * Creates a new {@link InterningAnnotatedTypeFactory} that operates on a particular AST.
+ * Creates a new {@link InterningAnnotatedTypeFactory}.
*
* @param checker the checker to use
*/
diff --git a/checker/src/main/java/org/checkerframework/checker/nonempty/NonEmptyAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nonempty/NonEmptyAnnotatedTypeFactory.java
new file mode 100644
index 00000000000..9cedb36f489
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nonempty/NonEmptyAnnotatedTypeFactory.java
@@ -0,0 +1,61 @@
+package org.checkerframework.checker.nonempty;
+
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.NewArrayTree;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import org.checkerframework.checker.nonempty.qual.NonEmpty;
+import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
+import org.checkerframework.common.basetype.BaseTypeChecker;
+import org.checkerframework.framework.type.AnnotatedTypeFactory;
+import org.checkerframework.framework.type.AnnotatedTypeMirror;
+import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
+import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
+import org.checkerframework.javacutil.AnnotationBuilder;
+
+/** The type factory for the {@link NonEmptyChecker}. */
+public class NonEmptyAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
+
+ /** The @{@link NonEmpty} annotation. */
+ public final AnnotationMirror NON_EMPTY = AnnotationBuilder.fromClass(elements, NonEmpty.class);
+
+ /**
+ * Creates a new {@link NonEmptyAnnotatedTypeFactory} that operates on a particular AST.
+ *
+ * @param checker the checker to use
+ */
+ public NonEmptyAnnotatedTypeFactory(BaseTypeChecker checker) {
+ super(checker);
+ this.sideEffectsUnrefineAliases = true;
+ this.postInit();
+ }
+
+ @Override
+ protected TreeAnnotator createTreeAnnotator() {
+ return new ListTreeAnnotator(super.createTreeAnnotator(), new NonEmptyTreeAnnotator(this));
+ }
+
+ /** The tree annotator for the Non-Empty Checker. */
+ private class NonEmptyTreeAnnotator extends TreeAnnotator {
+
+ /**
+ * Creates a new {@link NonEmptyTreeAnnotator}.
+ *
+ * @param aTypeFactory the type factory for this tree annotator
+ */
+ public NonEmptyTreeAnnotator(AnnotatedTypeFactory aTypeFactory) {
+ super(aTypeFactory);
+ }
+
+ @Override
+ public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) {
+ if (!type.hasEffectiveAnnotation(NON_EMPTY)) {
+ List extends ExpressionTree> initializers = tree.getInitializers();
+ if (initializers != null && !initializers.isEmpty()) {
+ type.replaceAnnotation(NON_EMPTY);
+ }
+ }
+ return super.visitNewArray(tree, type);
+ }
+ }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nonempty/NonEmptyChecker.java b/checker/src/main/java/org/checkerframework/checker/nonempty/NonEmptyChecker.java
new file mode 100644
index 00000000000..380d27ef8e8
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nonempty/NonEmptyChecker.java
@@ -0,0 +1,17 @@
+package org.checkerframework.checker.nonempty;
+
+import org.checkerframework.common.basetype.BaseTypeChecker;
+
+/**
+ * A type-checker that prevents {@link java.util.NoSuchElementException} in the use of container
+ * classes.
+ *
+ * @checker_framework.manual #non-empty-checker Non-Empty Checker
+ */
+public class NonEmptyChecker extends BaseTypeChecker {
+
+ /** Creates a NonEmptyChecker. */
+ public NonEmptyChecker() {
+ super();
+ }
+}
diff --git a/checker/src/main/java/org/checkerframework/checker/nonempty/NonEmptyTransfer.java b/checker/src/main/java/org/checkerframework/checker/nonempty/NonEmptyTransfer.java
new file mode 100644
index 00000000000..4726f7ac185
--- /dev/null
+++ b/checker/src/main/java/org/checkerframework/checker/nonempty/NonEmptyTransfer.java
@@ -0,0 +1,369 @@
+package org.checkerframework.checker.nonempty;
+
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.ExecutableElement;
+import org.checkerframework.checker.nonempty.qual.NonEmpty;
+import org.checkerframework.dataflow.analysis.TransferInput;
+import org.checkerframework.dataflow.analysis.TransferResult;
+import org.checkerframework.dataflow.cfg.node.CaseNode;
+import org.checkerframework.dataflow.cfg.node.EqualToNode;
+import org.checkerframework.dataflow.cfg.node.GreaterThanNode;
+import org.checkerframework.dataflow.cfg.node.GreaterThanOrEqualNode;
+import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode;
+import org.checkerframework.dataflow.cfg.node.LessThanNode;
+import org.checkerframework.dataflow.cfg.node.LessThanOrEqualNode;
+import org.checkerframework.dataflow.cfg.node.MethodAccessNode;
+import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
+import org.checkerframework.dataflow.cfg.node.Node;
+import org.checkerframework.dataflow.cfg.node.NotEqualNode;
+import org.checkerframework.dataflow.expression.JavaExpression;
+import org.checkerframework.dataflow.util.NodeUtils;
+import org.checkerframework.framework.flow.CFAnalysis;
+import org.checkerframework.framework.flow.CFStore;
+import org.checkerframework.framework.flow.CFTransfer;
+import org.checkerframework.framework.flow.CFValue;
+import org.checkerframework.javacutil.AnnotationUtils;
+import org.checkerframework.javacutil.TreeUtils;
+
+/**
+ * This class implements type rules that cannot be expressed via pre- or post-condition annotations.
+ */
+public class NonEmptyTransfer extends CFTransfer {
+
+ /** A {@link ProcessingEnvironment} instance. */
+ private final ProcessingEnvironment env;
+
+ /** A {@link NonEmptyAnnotatedTypeFactory} instance. */
+ protected final NonEmptyAnnotatedTypeFactory aTypeFactory;
+
+ /** The {@link java.util.Collection#size()} method. */
+ private final ExecutableElement collectionSize;
+
+ /** The {@link java.util.Map#size()} method. */
+ private final ExecutableElement mapSize;
+
+ /** The {@link java.util.List#indexOf(Object)} method. */
+ private final ExecutableElement listIndexOf;
+
+ /**
+ * Create a new {@link NonEmptyTransfer}.
+ *
+ * @param analysis the analysis for this transfer function
+ */
+ public NonEmptyTransfer(CFAnalysis analysis) {
+ super(analysis);
+
+ this.env = analysis.getTypeFactory().getProcessingEnv();
+ this.aTypeFactory = (NonEmptyAnnotatedTypeFactory) analysis.getTypeFactory();
+
+ this.collectionSize = TreeUtils.getMethod("java.util.Collection", "size", 0, this.env);
+ this.mapSize = TreeUtils.getMethod("java.util.Map", "size", 0, this.env);
+ this.listIndexOf = TreeUtils.getMethod("java.util.List", "indexOf", 1, this.env);
+ }
+
+ @Override
+ public TransferResult visitEqualTo(
+ EqualToNode n, TransferInput in) {
+ TransferResult result = super.visitEqualTo(n, in);
+
+ // The equality holds.
+ strengthenAnnotationSizeEquals(
+ in, n.getLeftOperand(), n.getRightOperand(), result.getThenStore());
+ refineGTE(n.getLeftOperand(), n.getRightOperand(), result.getThenStore());
+ refineGTE(n.getRightOperand(), n.getLeftOperand(), result.getThenStore());
+
+ // The equality does not hold.
+ refineNotEqual(n.getLeftOperand(), n.getRightOperand(), result.getElseStore());
+ refineNotEqual(n.getRightOperand(), n.getLeftOperand(), result.getElseStore());
+
+ return result;
+ }
+
+ @Override
+ public TransferResult visitNotEqual(
+ NotEqualNode n, TransferInput in) {
+ TransferResult result = super.visitNotEqual(n, in);
+
+ refineNotEqual(n.getLeftOperand(), n.getRightOperand(), result.getThenStore());
+ refineNotEqual(n.getRightOperand(), n.getLeftOperand(), result.getThenStore());
+
+ strengthenAnnotationSizeEquals(
+ in, n.getLeftOperand(), n.getRightOperand(), result.getElseStore());
+
+ return result;
+ }
+
+ @Override
+ public TransferResult visitLessThan(
+ LessThanNode n, TransferInput in) {
+ TransferResult result = super.visitLessThan(n, in);
+
+ // A < B is equivalent to B > A.
+ refineGT(n.getRightOperand(), n.getLeftOperand(), result.getThenStore());
+
+ // This handles the case where n < container.size().
+ refineGTE(n.getLeftOperand(), n.getRightOperand(), result.getElseStore());
+
+ return result;
+ }
+
+ @Override
+ public TransferResult visitLessThanOrEqual(
+ LessThanOrEqualNode n, TransferInput in) {
+ TransferResult result = super.visitLessThanOrEqual(n, in);
+
+ // A <= B is equivalent to B >= A.
+ // This handles the case where n <= container.size()
+ refineGTE(n.getRightOperand(), n.getLeftOperand(), result.getThenStore());
+
+ refineGT(n.getLeftOperand(), n.getRightOperand(), result.getElseStore());
+
+ return result;
+ }
+
+ @Override
+ public TransferResult visitGreaterThan(
+ GreaterThanNode n, TransferInput in) {
+ TransferResult result = super.visitGreaterThan(n, in);
+
+ refineGT(n.getLeftOperand(), n.getRightOperand(), result.getThenStore());
+
+ return result;
+ }
+
+ @Override
+ public TransferResult visitGreaterThanOrEqual(
+ GreaterThanOrEqualNode n, TransferInput in) {
+ TransferResult result = super.visitGreaterThanOrEqual(n, in);
+
+ refineGTE(n.getLeftOperand(), n.getRightOperand(), result.getThenStore());
+
+ return result;
+ }
+
+ @Override
+ public TransferResult visitCase(
+ CaseNode n, TransferInput in) {
+ TransferResult result = super.visitCase(n, in);
+ List caseOperands = n.getCaseOperands();
+ Node switchOpNode = n.getSwitchOperand().getExpression();
+
+ refineSwitchStatement(switchOpNode, caseOperands, result.getThenStore(), result.getElseStore());
+
+ return result;
+ }
+
+ /**
+ * Refine the transfer result's store, given the left- and right-hand side of an equality check
+ * comparing container sizes.
+ *
+ * @param in transfer input used to get the types of subnodes of {@code lhs} and {@code rhs}.
+ * @param lhs a node that may be a method invocation of {@link java.util.Collection size()} or
+ * {@link java.util.Map size()}
+ * @param rhs a node that may be a method invocation of {@link java.util.Collection size()} or
+ * {@link java.util.Map size()}
+ * @param store the "then" store of the comparison operation
+ */
+ private void strengthenAnnotationSizeEquals(
+ TransferInput in, Node lhs, Node rhs, CFStore store) {
+ if (!isSizeAccess(lhs) || !isSizeAccess(rhs)) {
+ return;
+ }
+
+ if (isAccessOfNonEmptyCollection(in, (MethodInvocationNode) lhs)) {
+ store.insertValue(getReceiverJE(rhs), aTypeFactory.NON_EMPTY);
+ } else if (isAccessOfNonEmptyCollection(in, (MethodInvocationNode) rhs)) {
+ store.insertValue(getReceiverJE(lhs), aTypeFactory.NON_EMPTY);
+ }
+ }
+
+ /**
+ * Returns true if the receiver of {@code methodAccessNode} is non-empty according to {@code in}.
+ *
+ * @param in used to get the type of {@code methodAccessNode}.
+ * @param methodAccessNode method access
+ * @return true if the receiver of {@code methodAccessNode} is non-empty according to {@code in}.
+ */
+ private boolean isAccessOfNonEmptyCollection(
+ TransferInput in, MethodInvocationNode methodAccessNode) {
+ Node receiver = methodAccessNode.getTarget().getReceiver();
+
+ return AnnotationUtils.containsSameByClass(
+ in.getValueOfSubNode(receiver).getAnnotations(), NonEmpty.class);
+ }
+
+ /**
+ * Updates the transfer result's store with information from the Non-Empty type system for
+ * expressions of the form {@code container.size() != n}, {@code n != container.size()}, or {@code
+ * container.indexOf(Object) != n}.
+ *
+ *
This method is always called twice, with the arguments reversed. So, it can do non-symmetric
+ * checks.
+ *
+ *
For example, the type of {@code container} in the "then" branch of a conditional statement
+ * with the test {@code container.size() != n} where {@code n} is 0 should refine to
+ * {@code @NonEmpty}.
+ *
+ *
This method is also used to refine the "else" store of an equality comparison where {@code
+ * container.size()} is compared against 0.
+ *
+ * @param left the left operand of a binary operation
+ * @param right the right operand of a binary operation
+ * @param store the abstract store to update
+ */
+ private void refineNotEqual(Node left, Node right, CFStore store) {
+ if (!(right instanceof IntegerLiteralNode)) {
+ return;
+ }
+ Integer emptyValue = emptyValue(left);
+ if (emptyValue == null) {
+ return;
+ }
+ // In case of a size() comparison, refine the store if the value is 0
+ // In case of a indexOf(Object) check, refine the store if the value is -1
+ IntegerLiteralNode integerLiteralNode = (IntegerLiteralNode) right;
+ if (integerLiteralNode.getValue() == (int) emptyValue) {
+ store.insertValue(getReceiverJE(left), aTypeFactory.NON_EMPTY);
+ }
+ }
+
+ /**
+ * Updates the transfer result's store with information from the Non-Empty type system for
+ * expressions of the form {@code container.size() > n} or {@code container.indexOf(Object) > n}.
+ *
+ *
When this method is called, {@link refineGTE} is also called, with the arguments reversed.
+ * So, this method can do non-symmetric checks.
+ *
+ *
For example, the type of {@code container} in the "then" branch of a conditional statement
+ * with the test {@code container.size() > n} where {@code n >= 0} should be refined to
+ * {@code @NonEmpty}.
+ *
+ * @param left the left operand of a binary operation
+ * @param right the right operand of a binary operation
+ * @param store the abstract store to update
+ */
+ private void refineGT(Node left, Node right, CFStore store) {
+ if (!(right instanceof IntegerLiteralNode)) {
+ return;
+ }
+ Integer emptyValue = emptyValue(left);
+ if (emptyValue == null) {
+ return;
+ }
+ // In case of a size() comparison, refine the store if the value is 0
+ // In case of a indexOf(Object) check, refine the store if the value is -1
+ IntegerLiteralNode integerLiteralNode = (IntegerLiteralNode) right;
+ if (integerLiteralNode.getValue() >= (int) emptyValue) {
+ store.insertValue(getReceiverJE(left), aTypeFactory.NON_EMPTY);
+ }
+ }
+
+ /**
+ * Updates the transfer result's store with information from the Non-Empty type system for
+ * expressions of the form {@code container.size() >= n} or {@code container.indexOf(Object) >=
+ * n}.
+ *
+ *
When this method is called, {@link refineGTE} is also called, with the arguments reversed.
+ * So, this method can do non-symmetric checks.
+ *
+ *
For example, the type of {@code container} in the "then" branch of a conditional statement
+ * with the test {@code container.size() >= n} where {@code n > 0} should be refined to
+ * {@code @NonEmpty}.
+ *
+ *
This method is also used to refine the "then" branch of an equality comparison where {@code
+ * container.size()} is compared against a non-zero value.
+ *
+ * @param left the left operand of a binary operation
+ * @param right the right operand of a binary operation
+ * @param store the abstract store to update
+ */
+ private void refineGTE(Node left, Node right, CFStore store) {
+ if (!(right instanceof IntegerLiteralNode)) {
+ return;
+ }
+ Integer emptyValue = emptyValue(left);
+ if (emptyValue == null) {
+ return;
+ }
+ // In case of a size() comparison, refine the store if the value is 0
+ // In case of a indexOf(Object) check, refine the store if the value is -1
+ IntegerLiteralNode integerLiteralNode = (IntegerLiteralNode) right;
+ if (integerLiteralNode.getValue() > (int) emptyValue) {
+ store.insertValue(getReceiverJE(left), aTypeFactory.NON_EMPTY);
+ }
+ }
+
+ /**
+ * Updates the transfer result's store with information from the Non-Empty type system for switch
+ * statements, where the test expression is of the form {@code container.size()} or {@code
+ * container.indexOf(Object)}.
+ *
+ *
For example, the "then" store of any case node with an integer value greater than 0 (or -1,
+ * in the case of the test expression being a call to {@code container.indexOf(Object)}) should
+ * refine the type of {@code container} to {@code @NonEmpty}.
+ *
+ * @param testNode a node that is the test expression for a {@code switch} statement
+ * @param caseOperands the operands within each case label
+ * @param thenStore the "then" store
+ * @param elseStore the "else" store, corresponding to the "default" case label
+ */
+ private void refineSwitchStatement(
+ Node testNode, List caseOperands, CFStore thenStore, CFStore elseStore) {
+ Integer emptyValue = emptyValue(testNode);
+ if (emptyValue == null) {
+ return;
+ }
+ for (Node caseOperand : caseOperands) {
+ if (!(caseOperand instanceof IntegerLiteralNode)) {
+ continue;
+ }
+ IntegerLiteralNode caseIntegerLiteral = (IntegerLiteralNode) caseOperand;
+ JavaExpression receiver = getReceiverJE(testNode);
+ CFStore storeToUpdate =
+ caseIntegerLiteral.getValue() > (int) emptyValue ? thenStore : elseStore;
+ storeToUpdate.insertValue(receiver, aTypeFactory.NON_EMPTY);
+ }
+ }
+
+ /**
+ * Return true if the given node is an invocation of {@link java.util.Collection#size()} or {@link
+ * java.util.Map#size()}.
+ *
+ * @param possibleSizeAccess a node that may be a method call to the {@code size()} method in the
+ * {@link java.util.Collection} or {@link java.util.Map} types
+ * @return true if the node is a method call to size()
+ */
+ private boolean isSizeAccess(Node possibleSizeAccess) {
+ return NodeUtils.isMethodInvocation(possibleSizeAccess, collectionSize, env)
+ || NodeUtils.isMethodInvocation(possibleSizeAccess, mapSize, env);
+ }
+
+ /**
+ * Return the receiver as a {@link JavaExpression} given a method invocation node.
+ *
+ * @param node a method invocation
+ * @return the receiver as a {@link JavaExpression}
+ */
+ private JavaExpression getReceiverJE(Node node) {
+ MethodAccessNode methodAccessNode = ((MethodInvocationNode) node).getTarget();
+ return JavaExpression.fromNode(methodAccessNode.getReceiver());
+ }
+
+ /**
+ * If this is an invocation of a size-dependent method, return the value that the method returns
+ * ffor an empty container.
+ *
+ * @param n a node that might be an invocation of a size-dependent method
+ * @return the value that the method returns ffor an empty container, or null
+ */
+ private Integer emptyValue(Node n) {
+ if (isSizeAccess(n)) {
+ return 0;
+ } else if (NodeUtils.isMethodInvocation(n, listIndexOf, env)) {
+ return -1;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/checker/src/test/java/org/checkerframework/checker/test/junit/NonEmptyTest.java b/checker/src/test/java/org/checkerframework/checker/test/junit/NonEmptyTest.java
new file mode 100644
index 00000000000..611cbfea5e3
--- /dev/null
+++ b/checker/src/test/java/org/checkerframework/checker/test/junit/NonEmptyTest.java
@@ -0,0 +1,24 @@
+package org.checkerframework.checker.test.junit;
+
+import java.io.File;
+import java.util.List;
+import org.checkerframework.framework.test.CheckerFrameworkPerDirectoryTest;
+import org.junit.runners.Parameterized.Parameters;
+
+/** JUnit tests for the Non-Empty Checker */
+public class NonEmptyTest extends CheckerFrameworkPerDirectoryTest {
+
+ /**
+ * Create a NonEmptyTest.
+ *
+ * @param testFiles the files containing test code to be type-checked
+ */
+ public NonEmptyTest(List testFiles) {
+ super(testFiles, org.checkerframework.checker.nonempty.NonEmptyChecker.class, "nonempty");
+ }
+
+ @Parameters
+ public static String[] getTestDirs() {
+ return new String[] {"nonempty", "all-systems"};
+ }
+}
diff --git a/checker/tests/nonempty/Comparisons.java b/checker/tests/nonempty/Comparisons.java
new file mode 100644
index 00000000000..20a2dd27e58
--- /dev/null
+++ b/checker/tests/nonempty/Comparisons.java
@@ -0,0 +1,238 @@
+import java.util.Arrays;
+import java.util.List;
+import org.checkerframework.checker.nonempty.qual.NonEmpty;
+
+class Comparisons {
+
+ /**** Tests for EQ ****/
+ void testEqZeroWithReturn(List strs) {
+ if (strs.size() == 0) {
+ // :: error: (method.invocation)
+ strs.iterator().next();
+ return;
+ }
+ strs.iterator().next(); // OK
+ }
+
+ void testEqZeroFallthrough(List strs) {
+ if (strs.size() == 0) {
+ // :: error: (method.invocation)
+ strs.iterator().next();
+ }
+ // :: error: (method.invocation)
+ strs.iterator().next();
+ }
+
+ void testEqNonZero(List strs) {
+ if (1 == strs.size()) {
+ strs.iterator().next();
+ } else {
+ // :: error: (method.invocation)
+ strs.iterator().next();
+ }
+ }
+
+ void testImplicitNonZero(List strs1, List strs2) {
+ if (strs1.isEmpty()) {
+ return;
+ }
+ if (strs1.size() == strs2.size()) {
+ @NonEmpty List strs3 = strs2; // OK
+ }
+ // :: error: (assignment)
+ @NonEmpty List strs4 = strs2;
+ }
+
+ void testImplicitNonZero2(List strs2) {
+ if (getNonEmptyList().size() == strs2.size()) {
+ @NonEmpty List strs3 = strs2; // OK
+ }
+ }
+
+ @NonEmpty
+ List getNonEmptyList() {
+ return Arrays.asList(new String[] {""});
+ }
+
+ void testEqualIndexOfRefinement(List