Skip to content

Commit bcbeb3c

Browse files
Register every ghost and state set before any spec is read
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 2c4f484 commit bcbeb3c

8 files changed

Lines changed: 124 additions & 15 deletions

File tree

‎liquidjava-example/src/main/java/testSuite/ErrorStateSetNames.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package testSuite;
22

33
import liquidjava.specification.Ghost;
4+
import liquidjava.specification.RefinementPredicate;
45
import liquidjava.specification.StateSet;
56

67
// A state set name must be a lowercase identifier that no state, ghost or other state set of the class uses
@@ -18,3 +19,7 @@ class ErrorStateSetNameOfAGhost {}
1819
@StateSet(name = "door", value = {"closed", "opened"}) // Expect: Error
1920
@StateSet(name = "door", value = {"unlocked", "locked"})
2021
class ErrorStateSetNameOfAnotherSet {}
22+
23+
@StateSet(name = "size", value = {"empty", "full"}) // Expect: Error
24+
@RefinementPredicate("int size(ErrorStateSetNameOfARefinementPredicate b)")
25+
class ErrorStateSetNameOfARefinementPredicate {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testSuite.classes.state_set_declared_later_correct;
2+
3+
import java.awt.Frame;
4+
5+
public class Client {
6+
public static void main(String[] args) {
7+
Frame f = new Frame();
8+
f.setUndecorated(true);
9+
f.pack();
10+
}
11+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package testSuite.classes.state_set_declared_later_correct;
2+
3+
import liquidjava.specification.ExternalRefinementsFor;
4+
import liquidjava.specification.StateRefinement;
5+
import liquidjava.specification.StateSet;
6+
7+
// uses the states of WindowRefinements, declared after it in this file
8+
@ExternalRefinementsFor("java.awt.Frame")
9+
public interface FrameRefinements {
10+
@StateRefinement(to = "notDisplayable(this)")
11+
void Frame();
12+
13+
@StateRefinement(from = "notDisplayable(this)")
14+
void setUndecorated(boolean undecorated);
15+
}
16+
17+
@ExternalRefinementsFor("java.awt.Window")
18+
@StateSet({"notDisplayable", "displayable"})
19+
interface WindowRefinements {
20+
@StateRefinement(to = "displayable(this)")
21+
void pack();
22+
}

‎liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import liquidjava.processor.ann_generation.FieldGhostsGeneration;
66
import liquidjava.processor.context.Context;
77
import liquidjava.processor.refinement_checker.ExternalRefinementTypeChecker;
8+
import liquidjava.processor.refinement_checker.GhostsFirstChecker;
89
import liquidjava.processor.refinement_checker.MethodsFirstChecker;
910
import liquidjava.processor.refinement_checker.RefinementTypeChecker;
1011
import spoon.processing.AbstractProcessor;
@@ -32,17 +33,27 @@ public boolean isToBeProcessed(CtPackage pkg) {
3233
public void process(CtPackage pkg) {
3334
Context c = Context.getInstance();
3435
c.reinitializeAllContext();
36+
registerGhosts(pkg, c);
3537
processPackage(pkg, c);
3638
}
3739

40+
/**
41+
* Registers the ghosts and state sets of every type of the package and its sub-packages, before any spec is read,
42+
* the ghosts of fields included
43+
*/
44+
private void registerGhosts(CtPackage pkg, Context c) {
45+
pkg.getTypes().forEach(type -> type.accept(new FieldGhostsGeneration(c, factory)));
46+
pkg.getTypes().forEach(type -> type.accept(new GhostsFirstChecker(c, factory)));
47+
pkg.getPackages().forEach(subPkg -> registerGhosts(subPkg, c));
48+
}
49+
3850
private void processPackage(CtPackage pkg, Context c) {
3951
try {
4052
// first pass: gather refinements. External specifications are registered for every type before any class's
4153
// own methods and constructors, which may rely on them (an unannotated constructor inherits the state of
4254
// the
4355
// specified super constructor it calls; an override is checked against its supertype's spec), whatever the
4456
// order of the types in the package.
45-
pkg.getTypes().forEach(type -> type.accept(new FieldGhostsGeneration(c, factory)));
4657
pkg.getTypes().forEach(type -> type.accept(new ExternalRefinementTypeChecker(c, factory)));
4758
pkg.getTypes().forEach(type -> type.accept(new MethodsFirstChecker(c, factory)));
4859

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

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,39 @@ public <T extends Enum<?>> void visitCtEnum(CtEnum<T> ctEnum) {
5454

5555
@Override
5656
public <T> void visitCtInterface(CtInterface<T> intrface) {
57-
Optional<CtAnnotation<?>> externalRef = getExternalRefinement(intrface);
58-
if (externalRef.isPresent()) {
59-
@SuppressWarnings("unchecked")
60-
CtLiteral<String> literal = (CtLiteral<String>) externalRef.get().getAllValues().get("value");
61-
this.prefix = literal.getValue();
57+
Optional<CtLiteral<String>> target = targetOf(intrface);
58+
if (target.isPresent()) {
59+
this.prefix = target.get().getValue();
6260
if (!classExists(prefix)) {
6361
String message = String.format("Could not find class '%s'", prefix);
64-
diagnostics.add(new ExternalClassNotFoundWarning(literal.getPosition(), message, prefix));
62+
diagnostics.add(new ExternalClassNotFoundWarning(target.get().getPosition(), message, prefix));
6563
return;
6664
}
6765
getRefinementFromAnnotation(intrface);
68-
handleStateSetsFromAnnotation(intrface);
6966
super.visitCtInterface(intrface);
7067
}
7168
}
7269

70+
/** Registers the ghosts and state sets of an external spec under the class it refines, when that class exists */
71+
public void registerGhosts(CtInterface<?> intrface) {
72+
Optional<CtLiteral<String>> target = targetOf(intrface);
73+
if (target.isEmpty() || !classExists(target.get().getValue()))
74+
return;
75+
this.prefix = target.get().getValue();
76+
try {
77+
handleStateSetsFromAnnotation(intrface);
78+
} catch (LJError e) {
79+
diagnostics.add(e);
80+
}
81+
}
82+
83+
/** The name of the class an external spec refines, as written in its annotation */
84+
@SuppressWarnings("unchecked")
85+
private Optional<CtLiteral<String>> targetOf(CtInterface<?> intrface) {
86+
return getExternalRefinement(intrface)
87+
.map(annotation -> (CtLiteral<String>) annotation.getAllValues().get("value"));
88+
}
89+
7390
@Override
7491
public <T> void visitCtField(CtField<T> f) {
7592
Optional<Predicate> oc = getRefinementFromAnnotation(f);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package liquidjava.processor.refinement_checker;
2+
3+
import liquidjava.diagnostics.Diagnostics;
4+
import liquidjava.diagnostics.errors.LJError;
5+
import liquidjava.processor.context.Context;
6+
import spoon.reflect.declaration.CtClass;
7+
import spoon.reflect.declaration.CtInterface;
8+
import spoon.reflect.declaration.CtType;
9+
import spoon.reflect.factory.Factory;
10+
11+
/**
12+
* First visit to the Spoon AST: registers the state sets and ghosts of every type, so that a spec read afterwards can
13+
* use those of any type, whatever the order of the types in the package or of the interfaces in a spec file
14+
*/
15+
public class GhostsFirstChecker extends TypeChecker {
16+
private final Diagnostics diagnostics = Diagnostics.getInstance();
17+
18+
public GhostsFirstChecker(Context context, Factory factory) {
19+
super(context, factory);
20+
}
21+
22+
@Override
23+
public <T> void visitCtClass(CtClass<T> ctClass) {
24+
register(ctClass);
25+
super.visitCtClass(ctClass);
26+
}
27+
28+
@Override
29+
public <T> void visitCtInterface(CtInterface<T> intrface) {
30+
if (getExternalRefinement(intrface).isPresent())
31+
new ExternalRefinementTypeChecker(context, factory).registerGhosts(intrface);
32+
else
33+
register(intrface);
34+
super.visitCtInterface(intrface);
35+
}
36+
37+
private void register(CtType<?> type) {
38+
try {
39+
handleStateSetsFromAnnotation(type);
40+
} catch (LJError e) {
41+
diagnostics.add(e);
42+
}
43+
}
44+
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ public <T> void visitCtClass(CtClass<T> ctClass) {
5858
// errors here should not prevent visiting methods, constructors or fields of the class
5959
try {
6060
getRefinementFromAnnotation(ctClass);
61-
handleStateSetsFromAnnotation(ctClass);
6261
} catch (LJError e) {
6362
diagnostics.add(e);
6463
}
@@ -87,7 +86,6 @@ public <T> void visitCtInterface(CtInterface<T> intrface) {
8786
// errors here should not prevent visiting the interface's methods
8887
try {
8988
getRefinementFromAnnotation(intrface);
90-
handleStateSetsFromAnnotation(intrface);
9189
} catch (LJError e) {
9290
diagnostics.add(e);
9391
}

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,6 @@ public Optional<Predicate> getRefinementFromAnnotation(CtElement element) throws
116116
String value = getStringFromAnnotation(ann.getValue("value"));
117117
ref = Optional.of(value);
118118

119-
} else if (an.contentEquals("liquidjava.specification.RefinementPredicate")) {
120-
CtExpression<String> rawValue = ann.getValue("value");
121-
String value = getStringFromAnnotation(rawValue);
122-
getGhostFunction(value, element, Utils.getAnnotationValuePosition(rawValue));
123-
124119
} else if (an.contentEquals("liquidjava.specification.RefinementAlias")) {
125120
CtExpression<String> rawValue = ann.getValue("value");
126121
String value = getStringFromAnnotation(rawValue);
@@ -198,6 +193,7 @@ public Optional<String> getMessageFromAnnotation(CtElement element) {
198193
return Optional.empty();
199194
}
200195

196+
/** Registers the ghosts and state sets a type declares, then checks the names given to its state sets */
201197
@SuppressWarnings("unchecked")
202198
public void handleStateSetsFromAnnotation(CtElement element) throws LJError {
203199
int set = 0;
@@ -218,6 +214,11 @@ public void handleStateSetsFromAnnotation(CtElement element) throws LJError {
218214
CtLiteral<String> s = (CtLiteral<String>) ann.getAllValues().get("value");
219215
createStateGhost(s.getValue(), element, Utils.getAnnotationValuePosition(s));
220216
}
217+
if (an.contentEquals("liquidjava.specification.RefinementPredicate")) {
218+
CtExpression<String> rawValue = ann.getValue("value");
219+
getGhostFunction(getStringFromAnnotation(rawValue), element,
220+
Utils.getAnnotationValuePosition(rawValue));
221+
}
221222
}
222223
// checked once every set and ghost of the class exists, so the order of the annotations does not matter
223224
for (Map.Entry<GhostFunction, SourcePosition> named : namedSets.entrySet())

0 commit comments

Comments
 (0)