1 /*
2  * Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * @test
26  * @summary Test compiler desugaring of a record type
27  * @library /tools/javac/lib
28  * @modules jdk.compiler
29  * @build   JavacTestingAbstractProcessor
30  * @compile --enable-preview -source ${jdk.version} TestRecordDesugar.java
31  * @run main/othervm --enable-preview TestRecordDesugar
32  */
33 
34 import java.io.*;
35 import java.lang.annotation.*;
36 import java.nio.file.*;
37 import javax.annotation.processing.*;
38 import javax.lang.model.*;
39 import javax.lang.model.element.*;
40 import javax.lang.model.type.*;
41 import javax.lang.model.util.*;
42 import java.util.*;
43 import java.util.spi.ToolProvider;
44 
45 /**
46  * Tests of the desugaring of record types.
47  */
48 public class TestRecordDesugar extends JavacTestingAbstractProcessor {
main(String... args)49     public static void main(String... args) {
50         String testSrc = System.getProperty("test.src");
51         String testClasspath = System.getProperty("test.class.path");
52         List<String> options = List.of(
53                 "--enable-preview",
54                 "-source", Integer.toString(Runtime.version().feature()),
55                 "-classpath", testClasspath,
56                 "-processor", "TestRecordDesugar",
57                 "-proc:only",
58                 Path.of(testSrc).resolve("TestRecordDesugar.java").toString()
59         );
60 
61         System.out.println("Options: " + options);
62         ToolProvider javac = ToolProvider.findFirst("javac").orElseThrow();
63         int rc = javac.run(System.out, System.err, options.toArray(new String[0]));
64         System.out.println("Return code: " + rc);
65         if (rc != 0) {
66             throw new AssertionError("unexpected return code: " + rc);
67         }
68     }
69 
70     int typeCount = 0;
71     int failures = 0;
72 
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)73     public boolean process(Set<? extends TypeElement> annotations,
74                           RoundEnvironment roundEnv) {
75        if (!roundEnv.processingOver()) {
76 
77            for(TypeElement nestedType :
78                    ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(TypeElementInfo.class))) {
79                typeCount++;
80                // elements.printElements(new PrintWriter(System.out), nestedType);
81                System.out.println("Testing " + nestedType.getQualifiedName());
82                failures += compareWithAnnotation(nestedType);
83            }
84 
85            if (typeCount <= 0) {
86                throw new RuntimeException("Failed to visit elements");
87            }
88 
89            if (failures > 0) {
90                throw new RuntimeException(failures + " failures");
91            }
92        }
93        return true;
94     }
95 
compareWithAnnotation(TypeElement nested)96     int compareWithAnnotation(TypeElement nested) {
97         int errors = 0;
98         TypeElementInfo infoOnNested = nested.getAnnotation(TypeElementInfo.class);
99 
100         // Build a map of (kind + name) to ElementInfo to allow easier
101         // lookups from the enclosed elements. The names of fields and
102         // methods may overlap so using name alone is not sufficient
103         // to disambiguate the elements. At this stage, do not use a
104         // record to store a combined key.
105         Map<String, ElementInfo> expectedInfoMap = new HashMap<>();
106         for (ElementInfo elementInfo : infoOnNested.elements()) {
107             String key = elementInfo.kind().toString() + " " + elementInfo.name();
108             // System.out.println("Testing " + key);
109             expectedInfoMap.put(elementInfo.kind().toString() + " " + elementInfo.name(),
110                                 elementInfo);
111         }
112 
113         for (Element enclosedElement : nested.getEnclosedElements()) {
114             System.out.println("\tChecking " + enclosedElement.getKind() + " " + enclosedElement);
115             String key = enclosedElement.getKind().toString() + " " + enclosedElement.getSimpleName();
116             ElementInfo expected = expectedInfoMap.get(key);
117             Objects.requireNonNull(expected, "\t\tMissing mapping for " + elementToString(enclosedElement));
118 
119             expectedInfoMap.remove(key);
120 
121             // Name and kind must already match; check other values are as expected
122 
123             // Modifiers
124             if (!enclosedElement.getModifiers().equals(Set.of(expected.modifiers()))) {
125                 errors++;
126                 System.out.println("\t\tUnexpected modifiers on " + enclosedElement + ":\t"
127                                    + enclosedElement.getModifiers());
128             }
129 
130             // TypeKind
131             TypeKind actualTypeKind = elementToTypeKind(enclosedElement);
132             if (!actualTypeKind.equals(expected.type())) {
133                 errors++;
134                 System.out.println("\t\tUnexpected type kind of  " +
135                                    actualTypeKind + " on " + enclosedElement + "; expected: "
136                                    + expected.type());
137             }
138 
139             // Elements.Origin informatoin
140             Elements.Origin actualOrigin = elements.getOrigin(enclosedElement);
141             if (!actualOrigin.equals(expected.origin())) {
142                 errors++;
143                 System.out.println("\t\tUnexpected origin of " +
144                                    actualOrigin + " on " + enclosedElement + "; expected: "
145                                    + expected.origin());
146             }
147         }
148 
149         if (!expectedInfoMap.isEmpty()) {
150             errors++;
151             for (String key : expectedInfoMap.keySet()) {
152                 System.out.println("\tError: unmatched elements: " +  key);
153             }
154         }
155        return errors;
156     }
157 
elementToString(Element element)158     private String elementToString(Element element) {
159         StringWriter sw = new StringWriter();
160         elements.printElements(sw, element);
161         return sw.toString();
162     }
163 
elementToTypeKind(Element element)164     private TypeKind elementToTypeKind(Element element) {
165         // Extract "primary type" from an element, the type of a field
166         // or state component, the return type of a method, etc.
167         return eltToTypeKindVisitor.visit(element);
168     }
169 
170     private static SimpleElementVisitor<TypeKind, Void> eltToTypeKindVisitor =
171         new SimpleElementVisitor<>() {
172         @Override
173         protected TypeKind defaultAction(Element e, Void p) {
174             return e.asType().getKind();
175         }
176 
177         @Override
178         public TypeKind visitExecutable(ExecutableElement e, Void p) {
179             return e.getReturnType().getKind();
180         }
181     };
182 
183     // Annotations to hold expected values
184 
185     @Retention(RetentionPolicy.RUNTIME)
186     @interface TypeElementInfo {
elements()187         ElementInfo[] elements() default {};
188     }
189 
190     @interface ElementInfo {
kind()191         ElementKind kind() default ElementKind.METHOD;
modifiers()192         Modifier[] modifiers() default {};
name()193         String name();
type()194         TypeKind type();
195         // parameters TBD
origin()196         Elements.Origin origin() default Elements.Origin.EXPLICIT;
197     }
198 
199     // Nested types subject to testing
200 
201     @TypeElementInfo(elements = {@ElementInfo(modifiers = {Modifier.PUBLIC, Modifier.ABSTRACT},
202                                               name = "modulus",
203                                               type = TypeKind.DOUBLE)})
204     interface ComplexNumber {
205         /**
206          * Return the magnitude of the complex number.
207          */
modulus()208         double modulus();
209     }
210 
211     /**
212      * Polar coordinate complex number.
213      *
214      * Expected type after desugaring:
215      *
216      *static record ComplexPolar(double r, double theta) implements TestRecordDesugar.ComplexNumber {
217      *  private final double r;
218      *  private final double theta;
219      *
220      *  @java.lang.Override
221      *  public double modulus();
222      *
223      *  public java.lang.String toString();
224      *
225      *  public final int hashCode();
226      *
227      *  public final boolean equals(java.lang.Object o);
228      *
229      *  public double r();
230      *
231      *  public double theta();
232      *}
233      */
234     @TypeElementInfo(elements =
235                      {@ElementInfo(kind = ElementKind.RECORD_COMPONENT,
236                                    modifiers = {Modifier.PUBLIC},
237                                    name = "r",
238                                    type = TypeKind.DOUBLE),
239 
240                       @ElementInfo(kind = ElementKind.FIELD,
241                                    modifiers = {Modifier.PRIVATE, Modifier.FINAL},
242                                    name = "r",
243                                    type = TypeKind.DOUBLE,
244                                    origin = Elements.Origin.EXPLICIT),
245 
246                       @ElementInfo(kind = ElementKind.RECORD_COMPONENT,
247                                    modifiers = {Modifier.PUBLIC},
248                                    name = "theta",
249                                    type = TypeKind.DOUBLE),
250 
251                       @ElementInfo(kind = ElementKind.FIELD,
252                                    modifiers = {Modifier.PRIVATE, Modifier.FINAL},
253                                    name = "theta",
254                                    type = TypeKind.DOUBLE,
255                                    origin = Elements.Origin.EXPLICIT),
256 
257                       @ElementInfo(modifiers = {Modifier.PUBLIC},
258                                    name = "modulus",
259                                    type = TypeKind.DOUBLE),
260 
261                       @ElementInfo(modifiers = {Modifier.PUBLIC, Modifier.FINAL},
262                                    name = "toString",
263                                    type = TypeKind.DECLARED,
264                                    origin = Elements.Origin.EXPLICIT),
265 
266                       @ElementInfo(modifiers = {Modifier.PUBLIC, Modifier.FINAL},
267                                    name = "hashCode",
268                                    type = TypeKind.INT,
269                                    origin = Elements.Origin.EXPLICIT),
270 
271                       @ElementInfo(modifiers = {Modifier.PUBLIC, Modifier.FINAL},
272                                    name = "equals",
273                                    type = TypeKind.BOOLEAN,
274                                    origin = Elements.Origin.EXPLICIT),
275 
276                       @ElementInfo(modifiers = {Modifier.PUBLIC},
277                                    name = "r",
278                                    type = TypeKind.DOUBLE,
279                                    origin = Elements.Origin.EXPLICIT),
280 
281                       @ElementInfo(modifiers = {Modifier.PUBLIC},
282                                    name = "theta",
283                                    type = TypeKind.DOUBLE,
284                                    origin = Elements.Origin.EXPLICIT),
285 
286                       @ElementInfo(kind = ElementKind.CONSTRUCTOR,
287                                    modifiers = {},
288                                    name = "<init>",
289                                    type = TypeKind.VOID,
290                                    origin = Elements.Origin.MANDATED),
291                              })
292    record ComplexPolar(double r, double theta) implements ComplexNumber {
293         @Override
modulus()294         public double modulus() {
295             return r;
296         }
297     }
298 
299     // Override equals in cartesian complex number record to allow
300     // testing of origin information.
301 
302     /**
303      * Cartesian coordinate complex number.
304      */
305     @TypeElementInfo(elements =
306                      {@ElementInfo(kind = ElementKind.RECORD_COMPONENT,
307                                    modifiers = {Modifier.PUBLIC},
308                                    name = "real",
309                                    type = TypeKind.DOUBLE),
310 
311                       @ElementInfo(kind = ElementKind.FIELD,
312                                    modifiers = {Modifier.PRIVATE, Modifier.FINAL},
313                                    name = "real",
314                                    type = TypeKind.DOUBLE,
315                                    origin = Elements.Origin.EXPLICIT),
316 
317                       @ElementInfo(kind = ElementKind.RECORD_COMPONENT,
318                                    modifiers = {Modifier.PUBLIC},
319                                    name = "imag",
320                                    type = TypeKind.DOUBLE),
321 
322                       @ElementInfo(kind = ElementKind.FIELD,
323                                    modifiers = {Modifier.PRIVATE, Modifier.FINAL},
324                                    name = "imag",
325                                    type = TypeKind.DOUBLE,
326                                    origin = Elements.Origin.EXPLICIT),
327 
328                       @ElementInfo(modifiers = {Modifier.PUBLIC},
329                                    name = "modulus",
330                                    type = TypeKind.DOUBLE),
331 
332                       @ElementInfo(modifiers = {Modifier.PUBLIC, Modifier.FINAL},
333                                    name = "toString",
334                                    type = TypeKind.DECLARED,
335                                    origin = Elements.Origin.EXPLICIT),
336 
337                       @ElementInfo(modifiers = {Modifier.PUBLIC, Modifier.FINAL},
338                                    name = "hashCode",
339                                    type = TypeKind.INT,
340                                    origin = Elements.Origin.EXPLICIT),
341 
342                       @ElementInfo(modifiers = {Modifier.PUBLIC},
343                                    name = "equals",
344                                    type = TypeKind.BOOLEAN),
345 
346                       @ElementInfo(modifiers = {Modifier.PUBLIC},
347                                    name = "real",
348                                    type = TypeKind.DOUBLE,
349                                    origin = Elements.Origin.EXPLICIT),
350 
351                       @ElementInfo(modifiers = {Modifier.PUBLIC},
352                                    name = "imag",
353                                    type = TypeKind.DOUBLE,
354                                    origin = Elements.Origin.EXPLICIT),
355 
356                       @ElementInfo(kind = ElementKind.FIELD,
357                                    modifiers = {Modifier.PRIVATE, Modifier.STATIC},
358                                    name = "PROJ_INFINITY",
359                                    type = TypeKind.DECLARED),
360 
361                       @ElementInfo(modifiers = {Modifier.PRIVATE},
362                                    name = "proj",
363                                    type = TypeKind.DECLARED),
364 
365                       @ElementInfo(kind = ElementKind.CONSTRUCTOR,
366                                    modifiers = {Modifier.PUBLIC},
367                                    name = "<init>",
368                                    type = TypeKind.VOID),
369                              })
370      record ComplexCartesian(double real, double imag) implements ComplexNumber {
371         // Explicit constructor declaration allowed
ComplexCartesian(double real, double imag)372         public ComplexCartesian(double real, double imag) {
373             this.real = real;
374             this.imag = imag;
375         }
376 
377         @Override
modulus()378         public double modulus() {
379             return StrictMath.hypot(real, imag);
380         }
381 
382         private static ComplexCartesian PROJ_INFINITY =
383             new ComplexCartesian(Double.POSITIVE_INFINITY, +0.0);
384 
385         // Make private rather than public to test mapping.
proj()386         private ComplexCartesian proj() {
387             if (Double.isInfinite(real) || Double.isInfinite(imag))
388                 return PROJ_INFINITY;
389             else
390                 return this;
391         }
392 
393         @Override
equals(Object o)394         public boolean equals(Object o) {
395             if (o instanceof ComplexCartesian) {
396                 ComplexCartesian that = (ComplexCartesian)o;
397 
398                 ComplexCartesian projThis = this.proj();
399                 ComplexCartesian projThat = that.proj();
400 
401                 // Don't worry about NaN values here
402                 return projThis.real == projThat.real &&
403                     projThis.imag == projThat.imag;
404             } else {
405                 return false;
406             }
407         }
408     }
409 }
410