Skip to content

Commit be47cb6

Browse files
rcosta358codex
andauthored
Fix Refinement Declaration Positions (#331)
## Description Highlight the predicate in “Refinement declared here” diagnostics by reading its annotation position directly, removing the dependency on parsing order. ### Example #### Before <img width="810" height="270" alt="image" src="https://github.com/user-attachments/assets/c112084f-70b5-4640-a205-2bd743932df5" /> #### After <img width="625" height="273" alt="image" src="https://github.com/user-attachments/assets/aa1323a9-5742-4077-a6c3-b28c79f39481" /> Validation: 206 tests passed, including declaration-position regression tests. ## Related Issues Stacked on #330. Merge it first, then retarget to `main`. 🤖 Generated with [Codex](https://openai.com/codex/) --------- Co-authored-by: Codex <noreply@openai.com>
1 parent 0e44432 commit be47cb6

6 files changed

Lines changed: 113 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: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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", "_ > 2.0");
22+
}
23+
24+
@Test
25+
void refinementErrorPointsToReturnPredicate() throws IOException {
26+
assertDeclarations("ErrorIdentity.java", "_ > 0");
27+
}
28+
29+
@Test
30+
void smtUnknownPointsToStatePredicate() throws IOException {
31+
assertDeclarations("ErrorSMTUnknownState.java", "amount(this) < limit");
32+
}
33+
34+
@Test
35+
void stateErrorPointsToStatePredicate() throws IOException {
36+
assertDeclarations("ErrorUnconstrainedStateRefinement.java", "ready(this)");
37+
}
38+
39+
@Test
40+
void declarationsCoverReturnsParametersLocalsAndFields() throws IOException {
41+
assertDeclarations("ErrorRefinementDeclarationPositions.java", "_ > 20", "_ > 30", "_ > 40", "_ > 10");
42+
}
43+
44+
private static void assertDeclarations(String file, String... predicates) throws IOException {
45+
Path path = Path.of("../liquidjava-example/src/main/java/testSuite/", file).toRealPath();
46+
String source = Files.readString(path);
47+
CommandLineLauncher.launch(path.toString());
48+
List<LJError> errors = Diagnostics.getInstance().getErrors().stream()
49+
.sorted(Comparator.comparingInt(error -> error.getPosition().getSourceStart())).toList();
50+
51+
assertEquals(predicates.length, errors.size());
52+
for (int i = 0; i < predicates.length; i++) {
53+
LJError error = errors.get(i);
54+
assertPredicatePosition(error.getDeclarationPosition(), path, source, predicates[i]);
55+
}
56+
}
57+
58+
private static void assertPredicatePosition(SourcePosition position, Path file, String source, String predicate)
59+
throws IOException {
60+
assertNotNull(position, "Missing refinement declaration position");
61+
assertTrue(position.isValidPosition(), "Invalid refinement declaration position");
62+
int start = source.indexOf("\"" + predicate + "\"") + 1;
63+
assertTrue(start > 0, "Predicate not found in fixture: " + predicate);
64+
assertEquals(start, position.getSourceStart());
65+
assertEquals(start + predicate.length() - 1, position.getSourceEnd());
66+
assertEquals(file, position.getFile().toPath().toRealPath());
67+
}
68+
69+
}

0 commit comments

Comments
 (0)