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 
MethodKind(boolean isAbstract)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 
TypeKind(String declStr, String typeStr, BoundKind boundKind, MethodKind... methodKinds)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 
compatibleSupertype(TypeKind tk)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 
CastKind(String castTemplate, int nbounds)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 
ExpressionKind(String exprString, boolean isFunctional)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 
CastInfo(CastKind kind, TypeKind... types)142         CastInfo(CastKind kind, TypeKind... types) {
143             this.kind = kind;
144             this.types = types;
145         }
146 
getCast()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 
wellFormed()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 
main(String... args)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 
allCastInfo()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 
IntersectionTargetTypeTest(CastInfo cInfo, ExpressionKind ek)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 
JavaSource()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
getCharContent(boolean ignoreEncodingErrors)253         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
254             return source;
255         }
256     }
257 
run(JavaCompiler tool, StandardJavaFileManager fm)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 
check()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 
mergeMethods(List<MethodKind> l1, List<MethodKind> l2)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 
report(Diagnostic<? extends JavaFileObject> diagnostic)331         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
332             if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
333                 errorFound = true;
334             }
335         }
336     }
337 }
338