Skip to content

Commit 8c5b7ec

Browse files
rcosta358codex
andauthored
Highlight predicates in refinement declaration diagnostics
Co-authored-by: Codex <noreply@openai.com>
1 parent 310bac3 commit 8c5b7ec

6 files changed

Lines changed: 124 additions & 7 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
import liquidjava.specification.StateRefinement;
5+
6+
public class ErrorRefinementDeclarationPositions {
7+
@Refinement("_ > 10")
8+
private int field = 11;
9+
10+
@StateRefinement(from = "true", to = "true")
11+
@Refinement(value = "_ > 20", msg = "result must exceed twenty")
12+
int result() {
13+
return 0; // Expect: Refinement Error
14+
}
15+
16+
void parameter(@Refinement("_ > 30") int value) {
17+
}
18+
19+
void check() {
20+
parameter(0); // Expect: Refinement Error
21+
}
22+
23+
void local() {
24+
@Refinement("_ > 40")
25+
int local = 41;
26+
local = 0; // Expect: Refinement Error
27+
}
28+
29+
void field() {
30+
field = 0; // Expect: Refinement Error
31+
}
32+
}

‎liquidjava-verifier/src/main/java/liquidjava/processor/context/PlacementInCode.java‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import java.lang.annotation.Annotation;
44

5-
import liquidjava.utils.constants.Keys;
5+
import liquidjava.utils.Utils;
66
import spoon.reflect.code.CtComment;
77
import spoon.reflect.cu.SourcePosition;
88
import spoon.reflect.declaration.CtAnnotation;
@@ -46,8 +46,7 @@ public static PlacementInCode createPlacement(CtElement elem) {
4646
}
4747
}
4848
String elemText = elemCopy.toString();
49-
SourcePosition annotationPosition = elem.getMetadata(Keys.REFINEMENT_POSITION)instanceof SourcePosition p ? p
50-
: elem.getPosition();
49+
SourcePosition annotationPosition = Utils.getRefinementPosition(elem);
5150
return new PlacementInCode(elemText, elem.getPosition(), annotationPosition);
5251
}
5352

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ public Optional<Predicate> getRefinementFromAnnotation(CtElement element) throws
9292
if (an.contentEquals("liquidjava.specification.Refinement")) {
9393
String value = getStringFromAnnotation(ann.getValue("value"));
9494
ref = Optional.of(value);
95-
element.putMetadata(Keys.REFINEMENT_POSITION, Utils.getLJAnnotationPosition(element, value));
9695

9796
} else if (an.contentEquals("liquidjava.specification.RefinementPredicate")) {
9897
CtExpression<String> rawValue = ann.getValue("value");
@@ -340,8 +339,7 @@ Optional<CtAnnotation<?>> getExternalRefinement(CtInterface<?> intrface) {
340339
public void checkVariableRefinements(Predicate refinementFound, String simpleName, CtTypeReference<?> type,
341340
CtElement usage, CtElement variable) throws LJError {
342341
Optional<Predicate> expectedType = getRefinementFromAnnotation(variable);
343-
SourcePosition declarationPosition = variable.getMetadata(Keys.REFINEMENT_POSITION)instanceof SourcePosition p
344-
? p : variable.getPosition();
342+
SourcePosition declarationPosition = Utils.getRefinementPosition(variable);
345343
Predicate cEt;
346344
RefinedVariable mainRV = null;
347345
if (context.hasVariable(simpleName)) {

‎liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.List;
44
import java.util.Map;
5+
import java.util.Objects;
56
import java.util.Scanner;
67
import java.util.Set;
78
import java.util.stream.Stream;
@@ -63,6 +64,14 @@ public static String getFile(CtElement element) {
6364
return pos.getFile().getAbsolutePath();
6465
}
6566

67+
public static SourcePosition getRefinementPosition(CtElement element) {
68+
return getLiquidJavaAnnotations(element)
69+
.filter(annotation -> annotation.getAnnotationType().getQualifiedName()
70+
.equals("liquidjava.specification.Refinement"))
71+
.map(annotation -> getAnnotationValuePosition(annotation.getValue("value"))).filter(Objects::nonNull)
72+
.findFirst().orElse(null);
73+
}
74+
6675
// Get the position of the annotation with the given value
6776
public static SourcePosition getLJAnnotationPosition(CtElement element, String value) {
6877
String quotedValue = "\"" + value + "\"";

‎liquidjava-verifier/src/main/java/liquidjava/utils/constants/Keys.java‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
public final class Keys {
44
public static final String REFINEMENT = "refinement";
5-
public static final String REFINEMENT_POSITION = "refinement_position";
65
public static final String REFINEMENT_SAT_CHECK = "refinement_sat_check";
76
public static final String TARGET = "target";
87
public static final String RETURN_VAR_NAME = "return_var_name";
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package liquidjava.api.tests;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import java.io.IOException;
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
import java.util.Comparator;
9+
import java.util.List;
10+
11+
import org.junit.jupiter.api.Test;
12+
13+
import liquidjava.api.CommandLineLauncher;
14+
import liquidjava.diagnostics.Diagnostics;
15+
import liquidjava.diagnostics.errors.LJError;
16+
import spoon.reflect.cu.SourcePosition;
17+
18+
class TestDeclarationPositions {
19+
@Test
20+
void smtUnknownPointsToReturnPredicate() throws IOException {
21+
assertDeclarations("ErrorSMTUnknown.java", "SMT Unknown Error", "_ > 2.0");
22+
}
23+
24+
@Test
25+
void refinementErrorPointsToReturnPredicate() throws IOException {
26+
assertDeclarations("ErrorIdentity.java", "Refinement Error", "_ > 0");
27+
}
28+
29+
@Test
30+
void smtUnknownPointsToStatePredicate() throws IOException {
31+
assertDeclarations("ErrorSMTUnknownState.java", "SMT Unknown Error", "amount(this) < limit");
32+
}
33+
34+
@Test
35+
void stateErrorPointsToStatePredicate() throws IOException {
36+
assertDeclarations("ErrorUnconstrainedStateRefinement.java", "State Refinement Error", "ready(this)");
37+
}
38+
39+
@Test
40+
void declarationsCoverReturnsParametersLocalsAndFields() throws IOException {
41+
assertDeclarations("ErrorRefinementDeclarationPositions.java", "Refinement Error", "_ > 20", "_ > 30", "_ > 40",
42+
"_ > 10");
43+
}
44+
45+
private static void assertDeclarations(String file, String title, String... predicates) throws IOException {
46+
Path path = Path.of("../liquidjava-example/src/main/java/testSuite/", file).toRealPath();
47+
String source = Files.readString(path);
48+
CommandLineLauncher.launch(path.toString());
49+
List<LJError> errors = Diagnostics.getInstance().getErrors().stream()
50+
.sorted(Comparator.comparingInt(error -> error.getPosition().getSourceStart())).toList();
51+
52+
assertEquals(predicates.length, errors.size());
53+
for (int i = 0; i < predicates.length; i++) {
54+
LJError error = errors.get(i);
55+
assertEquals(title, error.getTitle());
56+
assertPredicatePosition(error.getDeclarationPosition(), path, source, predicates[i]);
57+
assertPredicateUnderline(error, predicates[i]);
58+
}
59+
}
60+
61+
private static void assertPredicatePosition(SourcePosition position, Path file, String source, String predicate)
62+
throws IOException {
63+
assertNotNull(position, "Missing refinement declaration position");
64+
int start = source.indexOf("\"" + predicate + "\"") + 1;
65+
assertTrue(start > 0, "Predicate not found in fixture: " + predicate);
66+
assertEquals(start, position.getSourceStart());
67+
assertEquals(start + predicate.length() - 1, position.getSourceEnd());
68+
assertEquals(file, position.getFile().toPath().toRealPath());
69+
}
70+
71+
private static void assertPredicateUnderline(LJError error, String predicate) {
72+
String output = error.toString().replaceAll("\u001B\\[[;\\d]*m", "");
73+
String heading = "--> Refinement declared here:\n";
74+
assertTrue(output.contains(heading), "Missing refinement declaration snippet");
75+
String snippet = output.substring(output.indexOf(heading) + heading.length());
76+
String indent = " ".repeat(error.getDeclarationPosition().getColumn() - 1);
77+
String underline = "^".repeat(predicate.length());
78+
assertTrue(snippet.contains(indent + underline + "\n"), snippet);
79+
}
80+
}

0 commit comments

Comments
 (0)