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