1 /*
2  * Copyright (c) 2011, 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 7030150 7039931
27  * @summary Type inference for generic instance creation failed for formal type parameter
28  */
29 
30 import com.sun.source.util.JavacTask;
31 import java.net.URI;
32 import java.util.Arrays;
33 import javax.tools.Diagnostic;
34 import javax.tools.JavaCompiler;
35 import javax.tools.JavaFileObject;
36 import javax.tools.SimpleJavaFileObject;
37 import javax.tools.StandardJavaFileManager;
38 import javax.tools.ToolProvider;
39 
40 public class GenericConstructorAndDiamondTest {
41 
42     enum BoundKind {
43         NO_BOUND(""),
44         STRING_BOUND("extends String"),
45         INTEGER_BOUND("extends Integer");
46 
47         String boundStr;
48 
BoundKind(String boundStr)49         private BoundKind(String boundStr) {
50             this.boundStr = boundStr;
51         }
52 
matches(TypeArgumentKind tak)53         boolean matches(TypeArgumentKind tak) {
54             switch (tak) {
55                 case NONE: return true;
56                 case STRING: return this != INTEGER_BOUND;
57                 case INTEGER: return this != STRING_BOUND;
58                 default: return false;
59             }
60         }
61     }
62 
63     enum ConstructorKind {
64         NON_GENERIC("Foo(Object o) {}"),
65         GENERIC_NO_BOUND("<T> Foo(T t) {}"),
66         GENERIC_STRING_BOUND("<T extends String> Foo(T t) {}"),
67         GENERIC_INTEGER_BOUND("<T extends Integer> Foo(T t) {}");
68 
69         String constrStr;
70 
ConstructorKind(String constrStr)71         private ConstructorKind(String constrStr) {
72             this.constrStr = constrStr;
73         }
74 
matches(ArgumentKind ak)75         boolean matches(ArgumentKind ak) {
76             switch (ak) {
77                 case STRING: return this != GENERIC_INTEGER_BOUND;
78                 case INTEGER: return this != GENERIC_STRING_BOUND;
79                 default: return false;
80             }
81         }
82     }
83 
84     enum TypeArgArity {
85         ONE(1),
86         TWO(2),
87         THREE(3);
88 
89         int n;
90 
TypeArgArity(int n)91         private TypeArgArity(int n) {
92             this.n = n;
93         }
94     }
95 
96     enum TypeArgumentKind {
97         NONE(""),
98         STRING("String"),
99         INTEGER("Integer");
100 
101         String typeargStr;
102 
TypeArgumentKind(String typeargStr)103         private TypeArgumentKind(String typeargStr) {
104             this.typeargStr = typeargStr;
105         }
106 
getArgs(TypeArgArity arity)107         String getArgs(TypeArgArity arity) {
108             if (this == NONE) return "";
109             else {
110                 StringBuilder buf = new StringBuilder();
111                 String sep = "";
112                 for (int i = 0 ; i < arity.n ; i++) {
113                     buf.append(sep);
114                     buf.append(typeargStr);
115                     sep = ",";
116                 }
117                 return "<" + buf.toString() + ">";
118             }
119         }
120 
matches(ArgumentKind ak)121         boolean matches(ArgumentKind ak) {
122             switch (ak) {
123                 case STRING: return this != INTEGER;
124                 case INTEGER: return this != STRING;
125                 default: return false;
126             }
127         }
128 
matches(TypeArgumentKind other)129         boolean matches(TypeArgumentKind other) {
130             switch (other) {
131                 case STRING: return this != INTEGER;
132                 case INTEGER: return this != STRING;
133                 default: return true;
134             }
135         }
136     }
137 
138     enum ArgumentKind {
139         STRING("\"\""),
140         INTEGER("1");
141 
142         String argStr;
143 
ArgumentKind(String argStr)144         private ArgumentKind(String argStr) {
145             this.argStr = argStr;
146         }
147     }
148 
main(String... args)149     public static void main(String... args) throws Exception {
150 
151         //create default shared JavaCompiler - reused across multiple compilations
152         JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
153         StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null);
154 
155         for (BoundKind boundKind : BoundKind.values()) {
156             for (ConstructorKind constructorKind : ConstructorKind.values()) {
157                 for (TypeArgumentKind declArgKind : TypeArgumentKind.values()) {
158                     for (TypeArgArity arity : TypeArgArity.values()) {
159                         for (TypeArgumentKind useArgKind : TypeArgumentKind.values()) {
160                             for (TypeArgumentKind diamondArgKind : TypeArgumentKind.values()) {
161                                 for (ArgumentKind argKind : ArgumentKind.values()) {
162                                     new GenericConstructorAndDiamondTest(boundKind, constructorKind,
163                                             declArgKind, arity, useArgKind, diamondArgKind, argKind).run(comp, fm);
164                                 }
165                             }
166                         }
167                     }
168                 }
169             }
170         }
171     }
172 
173     BoundKind boundKind;
174     ConstructorKind constructorKind;
175     TypeArgumentKind declTypeArgumentKind;
176     TypeArgArity useTypeArgArity;
177     TypeArgumentKind useTypeArgumentKind;
178     TypeArgumentKind diamondTypeArgumentKind;
179     ArgumentKind argumentKind;
180     JavaSource source;
181     DiagnosticChecker diagChecker;
182 
GenericConstructorAndDiamondTest(BoundKind boundKind, ConstructorKind constructorKind, TypeArgumentKind declTypeArgumentKind, TypeArgArity useTypeArgArity, TypeArgumentKind useTypeArgumentKind, TypeArgumentKind diamondTypeArgumentKind, ArgumentKind argumentKind)183     GenericConstructorAndDiamondTest(BoundKind boundKind, ConstructorKind constructorKind,
184             TypeArgumentKind declTypeArgumentKind, TypeArgArity useTypeArgArity,
185             TypeArgumentKind useTypeArgumentKind, TypeArgumentKind diamondTypeArgumentKind,
186             ArgumentKind argumentKind) {
187         this.boundKind = boundKind;
188         this.constructorKind = constructorKind;
189         this.declTypeArgumentKind = declTypeArgumentKind;
190         this.useTypeArgArity = useTypeArgArity;
191         this.useTypeArgumentKind = useTypeArgumentKind;
192         this.diamondTypeArgumentKind = diamondTypeArgumentKind;
193         this.argumentKind = argumentKind;
194         this.source = new JavaSource();
195         this.diagChecker = new DiagnosticChecker();
196     }
197 
198     class JavaSource extends SimpleJavaFileObject {
199 
200         String template = "class Foo<X #BK> {\n" +
201                               "#CK\n" +
202                           "}\n" +
203                           "class Test {\n" +
204                               "void test() {\n" +
205                                  "Foo#TA1 f = new #TA2 Foo<#TA3>(#A);\n" +
206                               "}\n" +
207                           "}\n";
208 
209         String source;
210 
JavaSource()211         public JavaSource() {
212             super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
213             source = template.replace("#BK", boundKind.boundStr).
214                     replace("#CK", constructorKind.constrStr)
215                     .replace("#TA1", declTypeArgumentKind.getArgs(TypeArgArity.ONE))
216                     .replace("#TA2", useTypeArgumentKind.getArgs(useTypeArgArity))
217                     .replace("#TA3", diamondTypeArgumentKind.typeargStr)
218                     .replace("#A", argumentKind.argStr);
219         }
220 
221         @Override
getCharContent(boolean ignoreEncodingErrors)222         public CharSequence getCharContent(boolean ignoreEncodingErrors) {
223             return source;
224         }
225     }
226 
run(JavaCompiler tool, StandardJavaFileManager fm)227     void run(JavaCompiler tool, StandardJavaFileManager fm) throws Exception {
228         JavacTask ct = (JavacTask)tool.getTask(null, fm, diagChecker,
229                 null, null, Arrays.asList(source));
230         ct.analyze();
231         check();
232     }
233 
check()234     void check() {
235         boolean badActual = !constructorKind.matches(argumentKind);
236 
237         boolean badArity = constructorKind != ConstructorKind.NON_GENERIC &&
238                 useTypeArgumentKind != TypeArgumentKind.NONE &&
239                 useTypeArgArity != TypeArgArity.ONE;
240 
241         boolean badMethodTypeArg = constructorKind != ConstructorKind.NON_GENERIC &&
242                 !useTypeArgumentKind.matches(argumentKind);
243 
244         boolean badExplicitParams = (useTypeArgumentKind != TypeArgumentKind.NONE &&
245                 diamondTypeArgumentKind == TypeArgumentKind.NONE) ||
246                 !boundKind.matches(diamondTypeArgumentKind);
247 
248         boolean badGenericType = !boundKind.matches(declTypeArgumentKind) ||
249                 !diamondTypeArgumentKind.matches(declTypeArgumentKind);
250 
251         boolean shouldFail = badActual || badArity ||
252                 badMethodTypeArg || badExplicitParams || badGenericType;
253 
254         if (shouldFail != diagChecker.errorFound) {
255             throw new Error("invalid diagnostics for source:\n" +
256                 source.getCharContent(true) +
257                 "\nFound error: " + diagChecker.errorFound +
258                 "\nExpected error: " + shouldFail);
259         }
260     }
261 
262     static class DiagnosticChecker implements javax.tools.DiagnosticListener<JavaFileObject> {
263 
264         boolean errorFound;
265 
report(Diagnostic<? extends JavaFileObject> diagnostic)266         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
267             if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
268                 errorFound = true;
269             }
270         }
271     }
272 }
273