33 * SPDX-License-Identifier: GPL-2.0-only */
44package de .uka .ilkd .key .java .transformations .pipeline ;
55
6+ import java .util .Arrays ;
7+ import java .util .List ;
8+ import java .util .stream .Collectors ;
9+
610import com .github .javaparser .StaticJavaParser ;
711import com .github .javaparser .ast .CompilationUnit ;
12+ import com .github .javaparser .ast .Modifier ;
813import com .github .javaparser .ast .NodeList ;
914import com .github .javaparser .ast .body .*;
1015import com .github .javaparser .ast .expr .*;
1116import com .github .javaparser .ast .key .JmlDocModifier ;
1217import com .github .javaparser .ast .nodeTypes .NodeWithSimpleName ;
13- import com .github .javaparser .ast .stmt .BlockStmt ;
1418import com .github .javaparser .ast .stmt .ReturnStmt ;
19+ import com .github .javaparser .ast .type .PrimitiveType ;
1520import com .github .javaparser .ast .type .Type ;
1621import org .jspecify .annotations .Nullable ;
1722
18- import java .util .Arrays ;
19- import java .util .List ;
20- import java .util .stream .Collectors ;
21-
2223import static com .github .javaparser .ast .Modifier .DefaultKeyword .*;
23- import static de .uka .ilkd .key .java .transformations .AstFactory .*;
24+ import static de .uka .ilkd .key .java .transformations .AstFactory .assign ;
25+ import static de .uka .ilkd .key .java .transformations .AstFactory .attributeThis ;
2426
2527/// This transformation is made to transform any found [RecordDeclaration] into a corresponding
2628/// [ClassOrInterfaceDeclaration].
@@ -52,11 +54,9 @@ private ClassOrInterfaceDeclaration transform(RecordDeclaration recordDeclaratio
5254 clazz .addExtendedType (java .lang .Record .class );
5355 addGenerated (clazz );
5456
55- services .attachTypeSpec (clazz ,
56- equalFieldsEqualRecords (recordDeclaration ));
57-
5857 for (Parameter parameter : recordDeclaration .getParameters ()) {
59- FieldDeclaration field = clazz .addField (parameter .type (), parameter .getNameAsString (), PRIVATE , FINAL );
58+ FieldDeclaration field =
59+ clazz .addField (parameter .type (), parameter .getNameAsString (), PRIVATE , FINAL );
6060 field .getModifiers ().addAll (parameter .getModifiers ());
6161 addGenerated (field );
6262
@@ -71,21 +71,50 @@ private ClassOrInterfaceDeclaration transform(RecordDeclaration recordDeclaratio
7171 getter .getModifiers ().add (mod .clone ());
7272 }
7373 }
74- getter .getBody ().get ().addStatement (new ReturnStmt (parameter .getNameAsExpression ()));
74+ getter .getBody ().get ()
75+ .addStatement (new ReturnStmt (parameter .getNameAsExpression ()));
7576 addGenerated (getter );
7677 }
7778 }
7879
7980 createConstructor (recordDeclaration , clazz );
8081
81- createEquals (recordDeclaration , clazz );
82- createHashCode (recordDeclaration , clazz );
82+ var generated =
83+ createEquals (recordDeclaration , clazz ) && createHashCode (recordDeclaration , clazz );
84+
85+ if (generated ) {
86+ services .attachTypeSpec (clazz ,
87+ equalFieldsEqualRecords (recordDeclaration ));
88+
89+ services .attachTypeSpec (clazz ,
90+ "public static invariant_free (\\ forall %s a; a.equals(a));"
91+ .formatted (recordDeclaration .getNameAsString ()));
92+
93+ services .attachTypeSpec (clazz ,
94+ "public static invariant_free (\\ forall %s a,b; a.equals(b); b.equals(a));"
95+ .formatted (recordDeclaration .getNameAsString ()));
96+
97+ services .attachTypeSpec (clazz ,
98+ "public static invariant_free (\\ forall %s a,b,c; a.equals(b) && b.equals(c); a.equals(c));"
99+ .formatted (recordDeclaration .getNameAsString ()));
100+
101+ services .attachTypeSpec (clazz ,
102+ "public static invariant_free (\\ forall %s a,b; a.equals(b); a.hashCode() == b.hashCode());"
103+ .formatted (recordDeclaration .getNameAsString ()));
104+
105+ services .attachTypeSpec (clazz ,
106+ "public static invariant_free (\\ forall %s a,b; a.hashCode() != b.hashCode(); !a.equals(b));"
107+ .formatted (recordDeclaration .getNameAsString ()));
108+ }
109+
83110 createToString (recordDeclaration , clazz );
111+
84112 clazz .getMembers ().addAll (recordDeclaration .getMembers ());
85113 return clazz ;
86114 }
87115
88- private void createConstructor (RecordDeclaration recordDeclaration , ClassOrInterfaceDeclaration clazz ) {
116+ private void createConstructor (RecordDeclaration recordDeclaration ,
117+ ClassOrInterfaceDeclaration clazz ) {
89118 String [] paramTypes = recordDeclaration .getParameters ().stream ()
90119 .map (it -> it .getType ().asString ())
91120 .toArray (String []::new );
@@ -102,8 +131,21 @@ private void createConstructor(RecordDeclaration recordDeclaration, ClassOrInter
102131 for (var parameter : recordDeclaration .getParameters ()) {
103132 final var p = parameter .clone ();
104133 fullConstructor .addParameter (p );
105- body .addStatement (assign (attributeThis (p .getNameAsString ()), p .getNameAsExpression ()));
134+ body .addStatement (
135+ assign (attributeThis (p .getNameAsString ()), p .getNameAsExpression ()));
106136 }
137+
138+ var fieldParamEqual = recordDeclaration .getParameters ()
139+ .stream ()
140+ .map (NodeWithSimpleName ::getNameAsString )
141+ .map (it -> "ensures this.%s == %s;" .formatted (it , it ))
142+ .collect (Collectors .joining ("\n " ));
143+ services .attachSpec (fullConstructor , """
144+ public normal_behavior
145+ requires true;
146+ %s
147+ """ .formatted (fieldParamEqual ));
148+
107149 }
108150
109151 for (var compactConstructor : recordDeclaration .getCompactConstructors ()) {
@@ -113,7 +155,8 @@ private void createConstructor(RecordDeclaration recordDeclaration, ClassOrInter
113155 clazz .addMember (fullConstructor );
114156 }
115157
116- private void createToString (RecordDeclaration recordDeclaration , ClassOrInterfaceDeclaration clazz ) {
158+ private void createToString (RecordDeclaration recordDeclaration ,
159+ ClassOrInterfaceDeclaration clazz ) {
117160 boolean hasNoToString = recordDeclaration .getMethodsBySignature ("toString" ).isEmpty ();
118161 if (hasNoToString ) {
119162 MethodDeclaration toString = clazz .addMethod ("toString" , PUBLIC , FINAL , JML_NON_NULL );
@@ -136,7 +179,8 @@ private void createToString(RecordDeclaration recordDeclaration, ClassOrInterfac
136179 }
137180 }
138181
139- private void createHashCode (RecordDeclaration recordDeclaration , ClassOrInterfaceDeclaration clazz ) {
182+ private boolean createHashCode (RecordDeclaration recordDeclaration ,
183+ ClassOrInterfaceDeclaration clazz ) {
140184 boolean hasNoHashcode = recordDeclaration .getMethodsBySignature ("hashCode" ).isEmpty ();
141185 if (hasNoHashcode ) {
142186 MethodDeclaration hashCode = clazz .addMethod ("hashCode" , PUBLIC , FINAL );
@@ -147,40 +191,91 @@ private void createHashCode(RecordDeclaration recordDeclaration, ClassOrInterfac
147191 .map (it -> (Expression ) it )
148192 .toList ();
149193 final Expression call = callObjects ("hash" , args );
150- hashCode .getBody ().get ().addStatement (new ReturnStmt (call ));
194+ // hashCode.getBody().get().addStatement(new ReturnStmt(call));
195+ hashCode .setBody (null );
151196 addGenerated (hashCode );
197+
198+ services .attachSpec (hashCode , """
199+ public normal_behavior
200+ ensures true; requires true;
201+ assignable \\ strictly_nothing;
202+ """ );
152203 }
204+ return hasNoHashcode ;
153205 }
154206
155- private void createEquals (RecordDeclaration recordDeclaration , ClassOrInterfaceDeclaration clazz ) {
156- boolean hasNoEquals = recordDeclaration .getMethodsBySignature ("equals" , "java.lang.Object" ).isEmpty ()
157- && recordDeclaration .getMethodsBySignature ("equals" , "Object" ).isEmpty ();
207+ private boolean createEquals (RecordDeclaration recordDeclaration ,
208+ ClassOrInterfaceDeclaration clazz ) {
209+ boolean hasNoEquals =
210+ recordDeclaration .getMethodsBySignature ("equals" , "java.lang.Object" ).isEmpty ()
211+ && recordDeclaration .getMethodsBySignature ("equals" , "Object" ).isEmpty ();
158212 if (hasNoEquals ) {
159213 MethodDeclaration equals = clazz .addMethod ("equals" , PUBLIC , FINAL );
160214 equals .addAnnotation (Override .class );
161215 equals .setType (Boolean .TYPE );
162216 Type tObject = StaticJavaParser .parseType ("java.lang.Object" );
163- equals .getParameters ().add (new Parameter (tObject , "o" ));
164- BlockStmt body = equals .getBody ().get ();
165- body .addStatement (
166- mark (services .parseStatement ("if(this == o) return true;" )));
167- body .addStatement (
168- mark (services .parseStatement ("if(!(o instanceof %s)) return false;" .formatted (clazz .getNameAsString ()))));
169-
170- body .addStatement (
171- mark (services .parseStatement ("final %s that = (%s) o;"
172- .formatted (clazz .getNameAsString (), clazz .getNameAsString ()))));
173-
174- Expression equalFields = recordDeclaration .getParameters ().stream ()
175- .map (it -> callObjects ("equals" , it .getNameAsExpression (),
176- new FieldAccessExpr (new NameExpr ("that" ), it .getNameAsString ())))
177- .reduce ((a , b ) -> new BinaryExpr (a , b , BinaryExpr .Operator .AND ))
178- .orElse (new BooleanLiteralExpr (true ));
179- body .addStatement (mark (new ReturnStmt (equalFields )));
180-
181- body .addStatement (mark (new ReturnStmt (new BooleanLiteralExpr (true ))));
217+ var mods = new NodeList <>(new Modifier (JML_NULLABLE ));
218+ equals .getParameters ().add (new Parameter (mods , tObject , new SimpleName ("o" )));
219+ equals .setBody (null );
220+
221+
222+ services .attachSpec (equals , """
223+ public normal_behavior
224+ requires true;
225+ ensures (
226+ (this == o) //equality of identity
227+ || (o instanceof %s && o != null && %s)
228+ || (o instanceof %s && o != null && %s)
229+ ) <==> \\ result;
230+ ensures hashCode() != o.hashCode() ==> !\\ result;
231+ ensures o == null ==> !\\ result;
232+ assignable \\ strictly_nothing;
233+ """ .formatted (
234+ recordDeclaration .getNameAsString (),
235+ recordDeclaration .getParameters ().stream ()
236+ .map (NodeWithSimpleName ::getNameAsString )
237+ .map (it -> "this.%s==((%s)o).%s" .formatted (it ,
238+ recordDeclaration .getNameAsString (), it ))
239+ .collect (Collectors .joining (" && " )),
240+ recordDeclaration .getNameAsString (),
241+ recordDeclaration .getParameters ().stream ()
242+ .map (it -> {
243+ String pname = it .getNameAsString ();
244+ if (it .getType () instanceof PrimitiveType )
245+ return "(%s == ((%s)o).%s)" .formatted (pname ,
246+ recordDeclaration .getNameAsString (), pname );
247+ else
248+ return "( this.%s != null ? (this.%s.equals(((%s)o).%s)) : (o.%s == null))"
249+ .formatted (pname , pname ,
250+ recordDeclaration .getNameAsString (), pname , pname );
251+ })
252+ .collect (Collectors .joining (" && " ))));
253+
254+ /*
255+ * Nobody only contract:
256+ * BlockStmt body = equals.getBody().get();
257+ * body.addStatement(
258+ * mark(services.parseStatement("if(this == o) return true;")));
259+ * body.addStatement(
260+ * mark(services.parseStatement("if(!(o instanceof %s)) return false;".formatted(clazz.
261+ * getNameAsString()))));
262+ *
263+ * body.addStatement(
264+ * mark(services.parseStatement("final %s that = (%s) o;"
265+ * .formatted(clazz.getNameAsString(), clazz.getNameAsString()))));
266+ *
267+ * Expression equalFields = recordDeclaration.getParameters().stream()
268+ * .map(it -> callObjects("equals", it.getNameAsExpression(),
269+ * new FieldAccessExpr(new NameExpr("that"), it.getNameAsString())))
270+ * .reduce((a, b) -> new BinaryExpr(a, b, BinaryExpr.Operator.AND))
271+ * .orElse(new BooleanLiteralExpr(true));
272+ * body.addStatement(mark(new ReturnStmt(equalFields)));
273+ *
274+ * body.addStatement(mark(new ReturnStmt(new BooleanLiteralExpr(true))));
275+ */
182276 addGenerated (equals );
183277 }
278+ return hasNoEquals ;
184279 }
185280
186281 private static @ Nullable String equalFieldsEqualRecords (RecordDeclaration recordDeclaration ) {
@@ -193,18 +288,18 @@ private void createEquals(RecordDeclaration recordDeclaration, ClassOrInterfaceD
193288 .map (it -> "a.%s == b.%s" .formatted (it , it ))
194289 .collect (Collectors .joining (" && " ));
195290
196- return "public axiom (\\ forall %s a,b; %s; a.equals(b));" .formatted (
197- recordDeclaration .getNameAsString (),
198- identityOfFields
199- );
291+ return "public invariant_free (\\ forall %s a,b; %s; a.equals(b));" .formatted (
292+ recordDeclaration .getNameAsString (),
293+ identityOfFields );
200294 }
201295
202296 private Expression callObjects (String method , Expression ... exprs ) {
203297 return callObjects (method , Arrays .stream (exprs ).toList ());
204298 }
205299
206300 private Expression callObjects (String method , List <Expression > exprs ) {
207- var objects = new FieldAccessExpr (new FieldAccessExpr (new NameExpr ("java" ), "lang" ), "Objects" );
301+ var objects =
302+ new FieldAccessExpr (new FieldAccessExpr (new NameExpr ("java" ), "lang" ), "Objects" );
208303 return new MethodCallExpr (objects , method , new NodeList <>(exprs ));
209304 }
210305
0 commit comments