Skip to content

Commit 9b3276e

Browse files
Allow interfaces to be also annotated with external refinements (#244)
## Description Before, if we had interfaces as the external libraries that we were targeting, we would get no verification. Now we support that. ## Example Ignored before, now its okay. ```java @stateset({ "hasMore", "inNext", "notInNext" }) @ExternalRefinementsFor("java.util.Iterator") // Iterator is an Interface public interface IteratorRefinements<N> { ``` ## Related Issue [Reference any related issue] ## Type of change - [x] Bug fix - [x] New feature - [ ] Documentation update - [ ] Code refactoring ## Checklist - [x] Added/updated tests under `liquidjava-example/src/main/java/testSuite/` (`Correct*` / `Error*`) - [x] `mvn test` passes locally - [ ] Updated docs/README if behavior or API changed
1 parent 3ac0f36 commit 9b3276e

5 files changed

Lines changed: 92 additions & 2 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package testSuite.classes.iterator_interface_correct;
2+
3+
import liquidjava.specification.ExternalRefinementsFor;
4+
import liquidjava.specification.StateRefinement;
5+
import liquidjava.specification.StateSet;
6+
7+
/**
8+
* External refinements for the JDK interface {@code java.util.Iterator}. Exercises {@code @ExternalRefinementsFor} on an
9+
* interface target (not a concrete class) and a generic method ({@code N next()} vs the JDK's {@code E next()}).
10+
*/
11+
@StateSet({ "hasMore", "inNext", "notInNext" })
12+
@ExternalRefinementsFor("java.util.Iterator")
13+
public interface IteratorRefinements<N> {
14+
15+
@StateRefinement(to = "_ --> hasMore()")
16+
boolean hasNext();
17+
18+
@StateRefinement(from = "hasMore()", to = "inNext()")
19+
N next();
20+
21+
@StateRefinement(from = "inNext()", to = "notInNext()")
22+
void remove();
23+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package testSuite.classes.iterator_interface_correct;
2+
3+
import java.util.ArrayList;
4+
import java.util.Iterator;
5+
6+
public class Test {
7+
8+
public static void main(String[] args) {
9+
ArrayList<Object> list = new ArrayList<>();
10+
list.add(new Object());
11+
Iterator<Object> it = list.iterator();
12+
if (it.hasNext()) {
13+
it.next();
14+
it.remove(); // valid: remove() after next()
15+
}
16+
}
17+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package testSuite.classes.iterator_interface_error;
2+
3+
import liquidjava.specification.ExternalRefinementsFor;
4+
import liquidjava.specification.StateRefinement;
5+
import liquidjava.specification.StateSet;
6+
7+
/**
8+
* External refinements for the JDK interface {@code java.util.Iterator}. Exercises {@code @ExternalRefinementsFor} on an
9+
* interface target (not a concrete class) and a generic method ({@code N next()} vs the JDK's {@code E next()}).
10+
*/
11+
@StateSet({ "hasMore", "inNext", "notInNext" })
12+
@ExternalRefinementsFor("java.util.Iterator")
13+
public interface IteratorRefinements<N> {
14+
15+
@StateRefinement(to = "_ --> hasMore()")
16+
boolean hasNext();
17+
18+
@StateRefinement(from = "hasMore()", to = "inNext()")
19+
N next();
20+
21+
@StateRefinement(from = "inNext()", to = "notInNext()")
22+
void remove();
23+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package testSuite.classes.iterator_interface_error;
2+
3+
import java.util.ArrayList;
4+
import java.util.Iterator;
5+
6+
public class Test {
7+
8+
public static void main(String[] args) {
9+
ArrayList<Object> list = new ArrayList<>();
10+
list.add(new Object());
11+
Iterator<Object> it = list.iterator();
12+
it.remove(); // State Refinement Error
13+
}
14+
}

liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import spoon.reflect.declaration.CtParameter;
2525
import spoon.reflect.declaration.CtType;
2626
import spoon.reflect.factory.Factory;
27+
import spoon.reflect.reference.CtTypeParameterReference;
2728
import spoon.reflect.reference.CtTypeReference;
2829

2930
public class ExternalRefinementTypeChecker extends TypeChecker {
@@ -66,10 +67,13 @@ public <T> void visitCtField(CtField<T> f) {
6667

6768
public <R> void visitCtMethod(CtMethod<R> method) {
6869
CtType<?> targetType = factory.Type().createReference(prefix).getTypeDeclaration();
69-
if (!(targetType instanceof CtClass))
70+
if (!(targetType instanceof CtClass) && !(targetType instanceof CtInterface))
7071
return;
7172

72-
boolean isConstructor = method.getSimpleName().equals(targetType.getSimpleName());
73+
// Interfaces have no constructors; only a class can declare one, so the constructor-name shortcut
74+
// (a refinement method named like the target type) only applies to class targets.
75+
boolean isConstructor = targetType instanceof CtClass
76+
&& method.getSimpleName().equals(targetType.getSimpleName());
7377
if (isConstructor) {
7478
if (!constructorExists(targetType, method)) {
7579
String signature = method.getSignature();
@@ -149,9 +153,18 @@ private boolean typesMatch(CtTypeReference<?> type1, CtTypeReference<?> type2) {
149153
if (type1 == null || type2 == null)
150154
return false;
151155

156+
// Type variables (generics such as E, N, T) carry different names in the JDK type
157+
if (type1 instanceof CtTypeParameterReference t1 && type2 instanceof CtTypeParameterReference t2)
158+
return boundName(t1).equals(boundName(t2));
159+
152160
return type1.getQualifiedName().equals(type2.getQualifiedName());
153161
}
154162

163+
private static String boundName(CtTypeParameterReference ref) {
164+
CtTypeReference<?> bound = ref.getBoundingType();
165+
return bound == null ? "java.lang.Object" : bound.getQualifiedName();
166+
}
167+
155168
private boolean parametersMatch(List<?> targetParams, List<?> refinementParams) {
156169
if (targetParams.size() != refinementParams.size())
157170
return false;

0 commit comments

Comments
 (0)