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