Skip to content

Commit 956d550

Browse files
Check interface method bodies and calls without class-only assumptions
Fix issue #343 by resolving return contracts from the nearest method and its declaring type, including interface static and default methods. Use exact parameter types and keep lambda returns separate from enclosing methods. Add passing and failing interface regressions.
1 parent dcfc4eb commit 956d550

6 files changed

Lines changed: 97 additions & 12 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public interface CorrectInterfaceMethodBodies {
6+
static int size(String s) {
7+
int n = s.length();
8+
return n;
9+
}
10+
11+
@Refinement("_ > 0")
12+
static int positive() {
13+
return 1;
14+
}
15+
16+
static void checkStaticContract() {
17+
@Refinement("_ > 0")
18+
int n = positive();
19+
}
20+
21+
@Refinement("_ > 0")
22+
default int defaultPositive() {
23+
return 1;
24+
}
25+
26+
default CorrectInterfaceMethodBodies self() {
27+
return this;
28+
}
29+
30+
@Refinement("_ > 0")
31+
default int lambdaReturnIsNotMethodReturn() {
32+
java.util.function.IntSupplier supplier = () -> { return -1; };
33+
return 1;
34+
}
35+
36+
java.util.function.IntSupplier FIELD_SUPPLIER = () -> { return -1; };
37+
38+
@Refinement("_ > 0")
39+
static int overloaded(int value) { return 1; }
40+
41+
@Refinement("_ < 0")
42+
static int overloaded(boolean value) { return -1; }
43+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public interface ErrorInterfaceDefaultReturn {
6+
@Refinement("_ > 0")
7+
default int positive() {
8+
return -1; // Expect: Refinement Error
9+
}
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public interface ErrorInterfaceMethodCall {
6+
static void requirePositive(@Refinement("_ > 0") int value) {}
7+
8+
static void badCall() {
9+
requirePositive(-1); // Expect: Refinement Error
10+
}
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public interface ErrorInterfaceStaticReturn {
6+
@Refinement("_ > 0")
7+
static int positive() {
8+
return -1; // Expect: Refinement Error
9+
}
10+
}

‎liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@ public <T> void visitCtNewArray(CtNewArray<T> newArray) {
214214
@Override
215215
public <T> void visitCtThisAccess(CtThisAccess<T> thisAccess) {
216216
super.visitCtThisAccess(thisAccess);
217-
CtClass<?> c = thisAccess.getParent(CtClass.class);
217+
CtType<?> c = thisAccess.getParent(CtType.class);
218+
if (c == null)
219+
return;
218220
String s = c.getSimpleName();
219221
if (thisAccess.getParent() instanceof CtReturn) {
220222
String thisName = String.format(Formats.THIS, s);

‎liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import spoon.reflect.declaration.CtInterface;
3333
import spoon.reflect.declaration.CtMethod;
3434
import spoon.reflect.declaration.CtParameter;
35+
import spoon.reflect.declaration.CtType;
3536
import spoon.reflect.reference.CtExecutableReference;
3637
import spoon.reflect.reference.CtTypeReference;
3738

@@ -188,23 +189,31 @@ private Predicate handleFunctionRefinements(RefinedFunction f, CtElement method,
188189
}
189190

190191
public <R> void getReturnRefinements(CtReturn<R> ret) throws LJError {
191-
CtClass<?> c = ret.getParent(CtClass.class);
192-
String className = c.getSimpleName();
193192
if (ret.getReturnedExpression() == null) {
194193
return;
195194
}
196195

196+
// A return in a lambda belongs to that lambda, even when it is nested in a method.
197+
// Looking for any enclosing CtMethod would check it against the outer method's contract.
198+
CtExecutable<?> executable = ret.getParent(CtExecutable.class);
199+
if (!(executable instanceof CtMethod<?> method))
200+
return;
201+
CtType<?> owner = method.getDeclaringType();
202+
if (owner == null)
203+
return;
204+
197205
// check if there are refinements
198206
if (rtc.getRefinement(ret.getReturnedExpression()) == null)
199207
ret.getReturnedExpression().putMetadata(Keys.REFINEMENT, new Predicate());
200-
CtMethod<?> method = ret.getParent(CtMethod.class);
201-
202208
// check if method has refinements
203-
if (rtc.getRefinement(method) == null || !(method.getParent() instanceof CtClass))
209+
if (rtc.getRefinement(method) == null)
204210
return;
205211

206-
RefinedFunction fi = rtc.getContext().getFunction(method.getSimpleName(),
207-
((CtClass<?>) method.getParent()).getQualifiedName(), method.getParameters().size());
212+
List<CtTypeReference<?>> parameterTypes = new ArrayList<>();
213+
for (CtParameter<?> parameter : method.getParameters())
214+
parameterTypes.add(parameter.getType());
215+
RefinedFunction fi = rtc.getContext().getFunction(method.getSimpleName(), owner.getQualifiedName(),
216+
parameterTypes);
208217
if (fi == null)
209218
return;
210219

@@ -214,8 +223,8 @@ public <R> void getReturnRefinements(CtReturn<R> ret) throws LJError {
214223
}
215224

216225
// Both return and the method have metadata
217-
String thisName = String.format(Formats.THIS, className);
218-
rtc.getContext().addInstanceToContext(thisName, c.getReference(), new Predicate(), ret);
226+
String thisName = String.format(Formats.THIS, owner.getSimpleName());
227+
rtc.getContext().addInstanceToContext(thisName, owner.getReference(), new Predicate(), ret);
219228

220229
String returnVarName = String.format(Formats.RET, rtc.getContext().getCounter());
221230
Predicate cretRef = rtc.getRefinement(ret.getReturnedExpression())
@@ -241,8 +250,8 @@ public <R> void getInvocationRefinements(CtInvocation<R> invocation) throws LJEr
241250
if (cte != null)
242251
searchMethodInLibrary(cte, invocation);
243252

244-
} else if (method.getParent() instanceof CtClass) {
245-
String ctype = ((CtClass<?>) method.getParent()).getQualifiedName();
253+
} else if (method.getParent()instanceof CtType<?> owner) {
254+
String ctype = owner.getQualifiedName();
246255
List<CtTypeReference<?>> paramTypes = invocation.getExecutable().getParameters();
247256
RefinedFunction f = rtc.getContext().getFunction(method.getSimpleName(), ctype, paramTypes);
248257
if (f != null) { // inside rtc.context

0 commit comments

Comments
 (0)