1 /* 2 * Copyright (c) 2012, 2016, 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 8002099 8010822 27 * @summary Add support for intersection types in cast expression 28 * @modules jdk.compiler/com.sun.tools.javac.util 29 * @run main/othervm IntersectionTargetTypeTest 30 */ 31 32 import com.sun.source.util.JavacTask; 33 import com.sun.tools.javac.util.ListBuffer; 34 import java.net.URI; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.List; 38 import javax.tools.Diagnostic; 39 import javax.tools.JavaCompiler; 40 import javax.tools.JavaFileObject; 41 import javax.tools.SimpleJavaFileObject; 42 import javax.tools.StandardJavaFileManager; 43 import javax.tools.ToolProvider; 44 45 public class IntersectionTargetTypeTest { 46 47 static int checkCount = 0; 48 49 enum BoundKind { 50 INTF, 51 CLASS; 52 } 53 54 enum MethodKind { 55 NONE(false), 56 ABSTRACT_M(true), 57 DEFAULT_M(false), 58 ABSTRACT_G(true), 59 DEFAULT_G(false); 60 61 boolean isAbstract; 62 63 MethodKind(boolean isAbstract) { 64 this.isAbstract = isAbstract; 65 } 66 } 67 68 enum TypeKind { 69 A("interface A { }\n", "A", BoundKind.INTF, MethodKind.NONE), 70 B("interface B { default void m() { } }\n", "B", BoundKind.INTF, MethodKind.DEFAULT_M), 71 C("interface C { void m(); }\n", "C", BoundKind.INTF, MethodKind.ABSTRACT_M), 72 D("interface D extends B { }\n", "D", BoundKind.INTF, MethodKind.DEFAULT_M), 73 E("interface E extends C { }\n", "E", BoundKind.INTF, MethodKind.ABSTRACT_M), 74 F("interface F extends C { void g(); }\n", "F", BoundKind.INTF, MethodKind.ABSTRACT_G, MethodKind.ABSTRACT_M), 75 G("interface G extends B { void g(); }\n", "G", BoundKind.INTF, MethodKind.ABSTRACT_G, MethodKind.DEFAULT_M), 76 H("interface H extends A { void g(); }\n", "H", BoundKind.INTF, MethodKind.ABSTRACT_G), 77 OBJECT("", "Object", BoundKind.CLASS), 78 STRING("", "String", BoundKind.CLASS); 79 80 String declStr; 81 String typeStr; 82 BoundKind boundKind; 83 MethodKind[] methodKinds; 84 85 private TypeKind(String declStr, String typeStr, BoundKind boundKind, MethodKind... methodKinds) { 86 this.declStr = declStr; 87 this.typeStr = typeStr; 88 this.boundKind = boundKind; 89 this.methodKinds = methodKinds; 90 } 91 92 boolean compatibleSupertype(TypeKind tk) { 93 if (tk == this) return true; 94 switch (tk) { 95 case B: 96 return this != C && this != E && this != F; 97 case C: 98 return this != B && this != C && this != D && this != G; 99 case D: return compatibleSupertype(B); 100 case E: 101 case F: return compatibleSupertype(C); 102 case G: return compatibleSupertype(B); 103 case H: return compatibleSupertype(A); 104 default: 105 return true; 106 } 107 } 108 } 109 110 enum CastKind { 111 ONE_ARY("(#B0)", 1), 112 TWO_ARY("(#B0 & #B1)", 2), 113 THREE_ARY("(#B0 & #B1 & #B2)", 3); 114 115 String castTemplate; 116 int nbounds; 117 118 CastKind(String castTemplate, int nbounds) { 119 this.castTemplate = castTemplate; 120 this.nbounds = nbounds; 121 } 122 } 123 124 enum ExpressionKind { 125 LAMBDA("()->{}", true), 126 MREF("this::m", true), 127 //COND_LAMBDA("(true ? ()->{} : ()->{})", true), re-enable if spec allows this 128 //COND_MREF("(true ? this::m : this::m)", true), 129 STANDALONE("null", false); 130 131 String exprString; 132 boolean isFunctional; 133 134 private ExpressionKind(String exprString, boolean isFunctional) { 135 this.exprString = exprString; 136 this.isFunctional = isFunctional; 137 } 138 } 139 140 static class CastInfo { 141 CastKind kind; 142 TypeKind[] types; 143 144 CastInfo(CastKind kind, TypeKind... types) { 145 this.kind = kind; 146 this.types = types; 147 } 148 149 String getCast() { 150 String temp = kind.castTemplate; 151 for (int i = 0; i < kind.nbounds ; i++) { 152 temp = temp.replace(String.format("#B%d", i), types[i].typeStr); 153 } 154 return temp; 155 } 156 157 boolean wellFormed() { 158 //check for duplicate types 159 for (int i = 0 ; i < types.length ; i++) { 160 for (int j = 0 ; j < types.length ; j++) { 161 if (i != j && types[i] == types[j]) { 162 return false; 163 } 164 } 165 } 166 //check that classes only appear as first bound 167 boolean classOk = true; 168 for (int i = 0 ; i < types.length ; i++) { 169 if (types[i].boundKind == BoundKind.CLASS && 170 !classOk) { 171 return false; 172 } 173 classOk = false; 174 } 175 //check that supertypes are mutually compatible 176 for (int i = 0 ; i < types.length ; i++) { 177 for (int j = 0 ; j < types.length ; j++) { 178 if (!types[i].compatibleSupertype(types[j]) && i != j) { 179 return false; 180 } 181 } 182 } 183 return true; 184 } 185 } 186 187 public static void main(String... args) throws Exception { 188 //create default shared JavaCompiler - reused across multiple compilations 189 JavaCompiler comp = ToolProvider.getSystemJavaCompiler(); 190 try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) { 191 192 for (CastInfo cInfo : allCastInfo()) { 193 for (ExpressionKind ek : ExpressionKind.values()) { 194 new IntersectionTargetTypeTest(cInfo, ek).run(comp, fm); 195 } 196 } 197 System.out.println("Total check executed: " + checkCount); 198 } 199 } 200 201 static List<CastInfo> allCastInfo() { 202 ListBuffer<CastInfo> buf = new ListBuffer<>(); 203 for (CastKind kind : CastKind.values()) { 204 for (TypeKind b1 : TypeKind.values()) { 205 if (kind.nbounds == 1) { 206 buf.append(new CastInfo(kind, b1)); 207 continue; 208 } else { 209 for (TypeKind b2 : TypeKind.values()) { 210 if (kind.nbounds == 2) { 211 buf.append(new CastInfo(kind, b1, b2)); 212 continue; 213 } else { 214 for (TypeKind b3 : TypeKind.values()) { 215 buf.append(new CastInfo(kind, b1, b2, b3)); 216 } 217 } 218 } 219 } 220 } 221 } 222 return buf.toList(); 223 } 224 225 CastInfo cInfo; 226 ExpressionKind ek; 227 JavaSource source; 228 DiagnosticChecker diagChecker; 229 230 IntersectionTargetTypeTest(CastInfo cInfo, ExpressionKind ek) { 231 this.cInfo = cInfo; 232 this.ek = ek; 233 this.source = new JavaSource(); 234 this.diagChecker = new DiagnosticChecker(); 235 } 236 237 class JavaSource extends SimpleJavaFileObject { 238 239 String bodyTemplate = "class Test {\n" + 240 " void m() { }\n" + 241 " void test() {\n" + 242 " Object o = #C#E;\n" + 243 " } }"; 244 245 String source = ""; 246 247 public JavaSource() { 248 super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE); 249 for (TypeKind tk : TypeKind.values()) { 250 source += tk.declStr; 251 } 252 source += bodyTemplate.replaceAll("#C", cInfo.getCast()).replaceAll("#E", ek.exprString); 253 } 254 255 @Override 256 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 257 return source; 258 } 259 } 260 261 void run(JavaCompiler tool, StandardJavaFileManager fm) throws Exception { 262 JavacTask ct = (JavacTask)tool.getTask(null, fm, diagChecker, 263 null, null, Arrays.asList(source)); 264 try { 265 ct.analyze(); 266 } catch (Throwable ex) { 267 throw new AssertionError("Error thrown when compiling the following code:\n" + source.getCharContent(true)); 268 } 269 check(); 270 } 271 272 void check() { 273 checkCount++; 274 275 boolean errorExpected = !cInfo.wellFormed(); 276 277 if (ek.isFunctional) { 278 List<MethodKind> mks = new ArrayList<>(); 279 for (TypeKind tk : cInfo.types) { 280 if (tk.boundKind == BoundKind.CLASS) { 281 errorExpected = true; 282 break; 283 } else { 284 mks = mergeMethods(mks, Arrays.asList(tk.methodKinds)); 285 } 286 } 287 int abstractCount = 0; 288 for (MethodKind mk : mks) { 289 if (mk.isAbstract) { 290 abstractCount++; 291 } 292 } 293 errorExpected |= abstractCount != 1; 294 } 295 296 if (errorExpected != diagChecker.errorFound) { 297 throw new Error("invalid diagnostics for source:\n" + 298 source.getCharContent(true) + 299 "\nFound error: " + diagChecker.errorFound + 300 "\nExpected error: " + errorExpected); 301 } 302 } 303 304 List<MethodKind> mergeMethods(List<MethodKind> l1, List<MethodKind> l2) { 305 List<MethodKind> mergedMethods = new ArrayList<>(l1); 306 for (MethodKind mk2 : l2) { 307 boolean add = !mergedMethods.contains(mk2); 308 switch (mk2) { 309 case ABSTRACT_G: 310 add = add && !mergedMethods.contains(MethodKind.DEFAULT_G); 311 break; 312 case ABSTRACT_M: 313 add = add && !mergedMethods.contains(MethodKind.DEFAULT_M); 314 break; 315 case DEFAULT_G: 316 mergedMethods.remove(MethodKind.ABSTRACT_G); 317 case DEFAULT_M: 318 mergedMethods.remove(MethodKind.ABSTRACT_M); 319 case NONE: 320 add = false; 321 break; 322 } 323 if (add) { 324 mergedMethods.add(mk2); 325 } 326 } 327 return mergedMethods; 328 } 329 330 static class DiagnosticChecker implements javax.tools.DiagnosticListener<JavaFileObject> { 331 332 boolean errorFound; 333 334 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 335 if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { 336 errorFound = true; 337 } 338 } 339 } 340 }