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