Skip to content

Commit 1f343d7

Browse files
authored
Implement @EnsuresPresentIf postcondition annotation
1 parent d361ff8 commit 1f343d7

File tree

3 files changed

+178
-0
lines changed

3 files changed

+178
-0
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.checkerframework.checker.optional.qual;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
import org.checkerframework.framework.qual.ConditionalPostconditionAnnotation;
9+
import org.checkerframework.framework.qual.InheritedAnnotation;
10+
11+
/**
12+
* Indicates that the given expressions of type Optional<T> are present, if the method returns
13+
* the given result (either true or false).FOO
14+
*
15+
* <p>Here are ways this conditional postcondition annotation can be used:
16+
*
17+
* <p><b>Method parameters:</b> Suppose that a method has two arguments of type Optional&lt;T&gt;
18+
* and returns true if they are both equal <i>and</i> present. You could annotate the method as
19+
* follows:
20+
*
21+
* <pre><code> &nbsp;@EnsuresPresentIf(expression="#1", result=true)
22+
* &nbsp;@EnsuresPresentIf(expression="#2", result=true)
23+
* &nbsp;public &lt;T&gt; boolean isPresentAndEqual(Optional&lt;T&gt; optA, Optional&lt;T&gt; optB) { ... }</code>
24+
* </pre>
25+
*
26+
* because, if {@code isPresentAndEqual} returns true, then the first (#1) argument to {@code
27+
* isPresentAndEqual} was present, and so was the second (#2) argument. Note that you can write two
28+
* {@code @EnsurePresentIf} annotations on a single method.
29+
*
30+
* <p><b>Fields:</b> The value expressions can refer to fields, even private ones. For example:
31+
*
32+
* <pre><code> &nbsp;@EnsuresPresentIf(expression="this.optShape", result=true)
33+
* public boolean isShape() {
34+
* return (this.optShape != null &amp;&amp; this.optShape.isPresent());
35+
* }</code></pre>
36+
*
37+
* An {@code @EnsuresPresentIf} annotation that refers to a private field is useful for verifying
38+
* that client code performs needed checks in the right order, even if the client code cannot
39+
* directly affect the field.
40+
*
41+
* <p><b>Method postconditions:</b> Suppose that if a method {@code isRectangle()} returns true,
42+
* then {@code getRectangle()} will return a present Optional. You an express this relationship as:
43+
*
44+
* <pre>{@code @EnsuresPresentIf(result=true, expression="getRectangle()")
45+
* public @Pure isRectangle() { ... }}</pre>
46+
*
47+
* @see Present
48+
* @see EnsuresPresent
49+
* @checker_framework.manual #optional-checker Optional Checker
50+
*/
51+
@Documented
52+
@Retention(RetentionPolicy.RUNTIME)
53+
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
54+
@ConditionalPostconditionAnnotation(qualifier = Present.class)
55+
@InheritedAnnotation
56+
public @interface EnsuresPresentIf {
57+
/**
58+
* Returns the Java expressions of type Optional&lt;T&gt; that are present after the method
59+
* returns the given result.
60+
*
61+
* @return the Java expressions of type Optional&lt;T&gt; that are present after the method
62+
* returns the given result. value {@link #result()}
63+
* @checker_framework.manual #java-expressions-as-arguments Syntax of Java expressions
64+
*/
65+
String[] expression();
66+
67+
/**
68+
* Returns the return value of the method under which the postcondition holds.
69+
*
70+
* @return the return value of the method under which the postcondition holds.
71+
*/
72+
boolean result();
73+
74+
/**
75+
* A wrapper annotation that makes the {@link EnsuresPresentIf} annotation repeatable.
76+
*
77+
* <p>Programmers generally do not need to write this. It is created by Java when a programmer
78+
* writes more than one {@link EnsuresPresentIf} annotation at the same location.
79+
*/
80+
@Retention(RetentionPolicy.RUNTIME)
81+
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
82+
@ConditionalPostconditionAnnotation(qualifier = Present.class)
83+
public static @interface List {
84+
/**
85+
* Returns the repeatable annotations.
86+
*
87+
* @return the repeatable annotations.
88+
*/
89+
EnsuresPresentIf[] value();
90+
}
91+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import java.util.Optional;
2+
import org.checkerframework.checker.optional.qual.EnsuresPresentIf;
3+
import org.checkerframework.checker.optional.qual.Present;
4+
import org.checkerframework.dataflow.qual.Pure;
5+
import org.checkerframework.framework.qual.EnsuresQualifierIf;
6+
7+
public class EnsuresPresentIfTest {
8+
9+
// :: warning: (optional.field)
10+
private Optional<String> optId = Optional.of("abc");
11+
12+
@Pure
13+
public Optional<String> getOptId() {
14+
return Optional.of("abc");
15+
}
16+
17+
@EnsuresPresentIf(result = true, expression = "getOptId()")
18+
public boolean hasPresentId1() {
19+
return getOptId().isPresent();
20+
}
21+
22+
@EnsuresPresentIf(result = true, expression = "this.getOptId()")
23+
public boolean hasPresentId2() {
24+
return getOptId().isPresent();
25+
}
26+
27+
@EnsuresQualifierIf(result = true, expression = "getOptId()", qualifier = Present.class)
28+
public boolean hasPresentId3() {
29+
return getOptId().isPresent();
30+
}
31+
32+
@EnsuresQualifierIf(result = true, expression = "this.getOptId()", qualifier = Present.class)
33+
public boolean hasPresentId4() {
34+
return getOptId().isPresent();
35+
}
36+
37+
@EnsuresPresentIf(result = true, expression = "optId")
38+
public boolean hasPresentId5() {
39+
return optId.isPresent();
40+
}
41+
42+
@EnsuresPresentIf(result = true, expression = "this.optId")
43+
public boolean hasPresentId6() {
44+
return optId.isPresent();
45+
}
46+
47+
@EnsuresQualifierIf(result = true, expression = "optId", qualifier = Present.class)
48+
public boolean hasPresentId7() {
49+
return optId.isPresent();
50+
}
51+
52+
@EnsuresQualifierIf(result = true, expression = "this.optId", qualifier = Present.class)
53+
public boolean hasPresentId8() {
54+
return optId.isPresent();
55+
}
56+
57+
void client() {
58+
if (hasPresentId1()) {
59+
getOptId().get();
60+
}
61+
if (hasPresentId2()) {
62+
getOptId().get();
63+
}
64+
if (hasPresentId3()) {
65+
getOptId().get();
66+
}
67+
if (hasPresentId4()) {
68+
getOptId().get();
69+
}
70+
if (hasPresentId5()) {
71+
optId.get();
72+
}
73+
if (hasPresentId6()) {
74+
optId.get();
75+
}
76+
if (hasPresentId7()) {
77+
optId.get();
78+
}
79+
if (hasPresentId8()) {
80+
optId.get();
81+
}
82+
// :: error: (method.invocation)
83+
optId.get();
84+
}
85+
}

docs/manual/advanced-features.tex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,8 @@
12331233
\item \refqualclass{checker/nullness/qual}{RequiresNonNull}
12341234
\item \refqualclass{checker/nullness/qual}{EnsuresNonNull}
12351235
\item \refqualclass{checker/nullness/qual}{EnsuresNonNullIf}
1236+
\item \refqualclass{checker/optional/qual}{EnsuresPresent}
1237+
\item \refqualclass{checker/optional/qual}{EnsuresPresentIf}
12361238
% Not implemented: \refqualclass{checker/nullness/qual}{AssertNonNullIfNonNull}
12371239
\item \refqualclass{checker/nullness/qual}{KeyFor}
12381240
\item \refqualclass{checker/nullness/qual}{EnsuresKeyFor}

0 commit comments

Comments
 (0)