Skip to content

Commit 5013598

Browse files
Skip records in the external-spec pass (#370)
Fixes #369. ## Problem Since #363 (Spoon 11), any `record` crashed verification with an NPE in `TypeChecker.scan`. Spoon 11 dispatches records to `visitCtRecord`; `ExternalRefinementTypeChecker` overrides only `visitCtClass` (to skip user classes), so it walked the record's methods with `prefix == null` and `createReference(null)` threw. Building the error message then failed on the record's implicit members, which have no source file. ## Change - `ExternalRefinementTypeChecker.visitCtRecord`: skip, as for classes. - `TypeChecker.scan`: when the failing element has no source file, the message says so instead of throwing an NPE. With this, record methods are verified: a refinement violation inside a record method is reported (related to #342). ## Tests - `CorrectRecord`: a record with a method calling a refined method correctly; crashed on main, now passes. - `ErrorRecordMethod`: a violation in a record method is reported. - `mvn test` passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 43023fc commit 5013598

4 files changed

Lines changed: 37 additions & 1 deletion

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public record CorrectRecord(int x, int y) {
6+
7+
static int positive(@Refinement("_ > 0") int v) {
8+
return v;
9+
}
10+
11+
int sum() {
12+
return positive(1) + x + y;
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public record ErrorRecordMethod(int x, int y) {
6+
7+
static int positive(@Refinement("_ > 0") int v) {
8+
return v;
9+
}
10+
11+
int bad() {
12+
return positive(-1); // Expect: Refinement Error
13+
}
14+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import spoon.reflect.cu.SourcePosition;
1818
import spoon.reflect.declaration.CtAnnotation;
1919
import spoon.reflect.declaration.CtClass;
20+
import spoon.reflect.declaration.CtRecord;
2021
import spoon.reflect.declaration.CtElement;
2122
import spoon.reflect.declaration.CtField;
2223
import spoon.reflect.declaration.CtInterface;
@@ -39,6 +40,11 @@ public ExternalRefinementTypeChecker(Context context, Factory factory) {
3940
public <T> void visitCtClass(CtClass<T> ctClass) {
4041
}
4142

43+
@Override
44+
public void visitCtRecord(CtRecord ctRecord) {
45+
// records are user code, like classes: external specs are only read from interfaces
46+
}
47+
4248
@Override
4349
public <T> void visitCtInterface(CtInterface<T> intrface) {
4450
Optional<CtAnnotation<?>> externalRef = getExternalRefinement(intrface);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ public void scan(CtElement element) {
7171
throw e;
7272
} catch (RuntimeException e) {
7373
SourcePosition position = element.getPosition();
74-
String location = position.getFile().getAbsolutePath() + ":" + position.getLine();
74+
String location = position.isValidPosition() && position.getFile() != null
75+
? position.getFile().getAbsolutePath() + ":" + position.getLine()
76+
: "(no source position: " + element.getClass().getSimpleName() + ")";
7577
String expression = Utils.getExpressionFromPosition(position);
7678
String msg = String.format("\nError while checking %s\n on %s \n at %s\n with %s",
7779
element.getClass().getSimpleName(), expression, location, e.getMessage());

0 commit comments

Comments
 (0)