Skip to content

Commit 62a28a3

Browse files
authored
Add Custom Error Messages (#140)
1 parent 1b290cb commit 62a28a3

File tree

15 files changed

+125
-42
lines changed

15 files changed

+125
-42
lines changed

liquidjava-api/src/main/java/liquidjava/specification/Refinement.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@
1616
public @interface Refinement {
1717

1818
public String value();
19+
20+
public String msg() default "";
1921
}

liquidjava-api/src/main/java/liquidjava/specification/StateRefinement.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import java.lang.annotation.Target;
88

99
/**
10-
* Annotation to create state transitions in a method. The annotation has two arguments: from : the
10+
* Annotation to create state transitions in a method. The annotation has three arguments: from : the
1111
* state in which the object needs to be for the method to be invoked correctly to : the state in
12-
* which the object will be after the execution of the method
12+
* which the object will be after the execution of the method msg : optional custom error message to display when refinement is violated
1313
* e.g. @StateRefinement(from="open(this)", to="closed(this)")
1414
*
1515
* @author catarina gamboa
@@ -21,4 +21,6 @@
2121
public String from() default "";
2222

2323
public String to() default "";
24+
25+
public String msg() default "";
2426
}

liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ public class LJDiagnostic extends RuntimeException {
1111
private final String title;
1212
private final String message;
1313
private final String accentColor;
14+
private final String customMessage;
1415
private String file;
1516
private ErrorPosition position;
17+
private static final String PIPE = " | ";
1618

17-
public LJDiagnostic(String title, String message, SourcePosition pos, String accentColor) {
19+
public LJDiagnostic(String title, String message, SourcePosition pos, String accentColor, String customMessage) {
1820
this.title = title;
1921
this.message = message;
2022
this.file = (pos != null && pos.getFile() != null) ? pos.getFile().getPath() : null;
2123
this.position = ErrorPosition.fromSpoonPosition(pos);
2224
this.accentColor = accentColor;
25+
this.customMessage = customMessage;
2326
}
2427

2528
public String getTitle() {
@@ -30,6 +33,10 @@ public String getMessage() {
3033
return message;
3134
}
3235

36+
public String getCustomMessage() {
37+
return customMessage;
38+
}
39+
3340
public String getDetails() {
3441
return ""; // to be overridden by subclasses
3542
}
@@ -104,19 +111,26 @@ public String getSnippet() {
104111
String line = lines.get(i - 1);
105112

106113
// add line
107-
sb.append(Colors.GREY).append(lineNumStr).append(" | ").append(line).append(Colors.RESET).append("\n");
114+
sb.append(Colors.GREY).append(lineNumStr).append(PIPE).append(line).append(Colors.RESET).append("\n");
108115

109116
// add error markers on the line(s) with the error
110117
if (i >= position.lineStart() && i <= position.lineEnd()) {
111118
int colStart = (i == position.lineStart()) ? position.colStart() : 1;
112119
int colEnd = (i == position.lineEnd()) ? position.colEnd() : line.length();
113120

114121
if (colStart > 0 && colEnd > 0) {
115-
// line number padding + " | " + column offset
116-
String indent = " ".repeat(padding) + Colors.GREY + " | " + Colors.RESET
122+
// line number padding + pipe + column offset
123+
String indent = " ".repeat(padding) + Colors.GREY + PIPE + Colors.RESET
117124
+ " ".repeat(colStart - 1);
118-
String markers = accentColor + "^".repeat(Math.max(1, colEnd - colStart + 1)) + Colors.RESET;
119-
sb.append(indent).append(markers).append("\n");
125+
String markers = accentColor + "^".repeat(Math.max(1, colEnd - colStart + 1));
126+
sb.append(indent).append(markers);
127+
128+
// custom message
129+
if (customMessage != null && !customMessage.isBlank()) {
130+
String offset = " ".repeat(padding + colEnd + PIPE.length() + 1);
131+
sb.append(" " + customMessage.replace("\n", "\n" + offset));
132+
}
133+
sb.append(Colors.RESET).append("\n");
120134
}
121135
}
122136
}

liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ public abstract class LJError extends LJDiagnostic {
1313
private final TranslationTable translationTable;
1414

1515
public LJError(String title, String message, SourcePosition pos, TranslationTable translationTable) {
16-
super(title, message, pos, Colors.BOLD_RED);
16+
this(title, message, pos, translationTable, null);
17+
}
18+
19+
public LJError(String title, String message, SourcePosition pos, TranslationTable translationTable,
20+
String customMessage) {
21+
super(title, message, pos, Colors.BOLD_RED, customMessage);
1722
this.translationTable = translationTable != null ? translationTable : new TranslationTable();
1823
}
1924

liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ public class RefinementError extends LJError {
1616
private final ValDerivationNode found;
1717

1818
public RefinementError(SourcePosition position, ValDerivationNode expected, ValDerivationNode found,
19-
TranslationTable translationTable) {
19+
TranslationTable translationTable, String customMessage) {
2020
super("Refinement Error", String.format("%s is not a subtype of %s", found.getValue(), expected.getValue()),
21-
position, translationTable);
21+
position, translationTable, customMessage);
2222
this.expected = expected;
2323
this.found = found;
2424
}

liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ public class StateRefinementError extends LJError {
1515
private final String found;
1616

1717
public StateRefinementError(SourcePosition position, Expression expected, Expression found,
18-
TranslationTable translationTable) {
18+
TranslationTable translationTable, String customMessage) {
1919
super("State Refinement Error", String.format("Expected state %s but found %s", expected.toSimplifiedString(),
20-
found.toSimplifiedString()), position, translationTable);
20+
found.toSimplifiedString()), position, translationTable, customMessage);
2121
this.expected = expected.toSimplifiedString();
2222
this.found = found.toSimplifiedString();
2323
}

liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@
1010
public abstract class LJWarning extends LJDiagnostic {
1111

1212
public LJWarning(String message, SourcePosition pos) {
13-
super("Warning", message, pos, Colors.BOLD_YELLOW);
13+
super("Warning", message, pos, Colors.BOLD_YELLOW, null);
1414
}
1515
}

liquidjava-verifier/src/main/java/liquidjava/processor/context/ObjectState.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public class ObjectState {
66

77
Predicate from;
88
Predicate to;
9+
String message;
910

1011
public ObjectState() {
1112
}
@@ -15,6 +16,12 @@ public ObjectState(Predicate from, Predicate to) {
1516
this.to = to;
1617
}
1718

19+
public ObjectState(Predicate from, Predicate to, String message) {
20+
this.from = from;
21+
this.to = to;
22+
this.message = message;
23+
}
24+
1825
public void setFrom(Predicate from) {
1926
this.from = from;
2027
}
@@ -39,8 +46,16 @@ public Predicate getTo() {
3946
return to != null ? to : new Predicate();
4047
}
4148

49+
public String getMessage() {
50+
return message;
51+
}
52+
53+
public void setMessage(String message) {
54+
this.message = message;
55+
}
56+
4257
public ObjectState clone() {
43-
return new ObjectState(from.clone(), to.clone());
58+
return new ObjectState(from.clone(), to.clone(), message);
4459
}
4560

4661
@Override

liquidjava-verifier/src/main/java/liquidjava/processor/context/Refined.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public abstract class Refined {
88
private String name; // y
99
private CtTypeReference<?> type; // int
1010
private Predicate refinement; // 9 <= y && y <= 100
11+
private String message;
1112

1213
public Refined() {
1314
}
@@ -44,6 +45,14 @@ public Predicate getRefinement() {
4445
return new Predicate();
4546
}
4647

48+
public String getMessage() {
49+
return message;
50+
}
51+
52+
public void setMessage(String message) {
53+
this.message = message;
54+
}
55+
4756
public Predicate getRenamedRefinements(String toReplace) {
4857
return refinement.substituteVariable(name, toReplace);
4958
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,10 @@ public <T> void visitCtLocalVariable(CtLocalVariable<T> localVariable) {
124124
super.visitCtLocalVariable(localVariable);
125125
// only declaration, no assignment
126126
if (localVariable.getAssignment() == null) {
127-
Optional<Predicate> a;
128-
a = getRefinementFromAnnotation(localVariable);
129-
context.addVarToContext(localVariable.getSimpleName(), localVariable.getType(), a.orElse(new Predicate()),
130-
localVariable);
127+
Optional<Predicate> a = getRefinementFromAnnotation(localVariable);
128+
RefinedVariable v = context.addVarToContext(localVariable.getSimpleName(), localVariable.getType(),
129+
a.orElse(new Predicate()), localVariable);
130+
getMessageFromAnnotation(localVariable).ifPresent(v::setMessage);
131131
} else {
132132
String varName = localVariable.getSimpleName();
133133
CtExpression<?> e = localVariable.getAssignment();
@@ -238,6 +238,7 @@ public <T> void visitCtField(CtField<T> f) {
238238
ret = c.get().substituteVariable(Keys.WILDCARD, name).substituteVariable(f.getSimpleName(), name);
239239
}
240240
RefinedVariable v = context.addVarToContext(name, f.getType(), ret, f);
241+
getMessageFromAnnotation(f).ifPresent(v::setMessage);
241242
if (v instanceof Variable) {
242243
((Variable) v).setLocation("this");
243244
}

0 commit comments

Comments
 (0)