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