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