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