Skip to content

Commit 4c7d8ad

Browse files
committed
GROOVY-10033
1 parent 52a10ff commit 4c7d8ad

File tree

20 files changed

+1259
-54
lines changed

20 files changed

+1259
-54
lines changed

base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GenericsTests.java

+283-29
Large diffs are not rendered by default.

base/org.codehaus.groovy25/.checkstyle

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
<file-match-pattern match-pattern="groovy/control/CompilationUnit.java" include-pattern="false" />
6161
<file-match-pattern match-pattern="groovy/control/CompilerConfiguration.java" include-pattern="false" />
6262
<file-match-pattern match-pattern="groovy/control/ErrorCollector.java" include-pattern="false" />
63+
<file-match-pattern match-pattern="groovy/control/GenericsVisitor.java" include-pattern="false" />
6364
<file-match-pattern match-pattern="groovy/control/ProcessingUnit.java" include-pattern="false" />
6465
<file-match-pattern match-pattern="groovy/control/ResolveVisitor.java" include-pattern="false" />
6566
<file-match-pattern match-pattern="groovy/control/SourceUnit.java" include-pattern="false" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.codehaus.groovy.control;
20+
21+
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
22+
import org.codehaus.groovy.ast.ClassNode;
23+
import org.codehaus.groovy.ast.FieldNode;
24+
import org.codehaus.groovy.ast.GenericsType;
25+
import org.codehaus.groovy.ast.InnerClassNode;
26+
import org.codehaus.groovy.ast.MethodNode;
27+
import org.codehaus.groovy.ast.Parameter;
28+
import org.codehaus.groovy.ast.expr.ArrayExpression;
29+
import org.codehaus.groovy.ast.expr.CastExpression;
30+
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
31+
import org.codehaus.groovy.ast.expr.DeclarationExpression;
32+
import org.codehaus.groovy.ast.expr.Expression;
33+
import org.codehaus.groovy.ast.expr.TupleExpression;
34+
import org.codehaus.groovy.transform.trait.Traits;
35+
36+
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUnboundedWildcard;
37+
38+
/**
39+
* Verify correct usage of generics.
40+
* This includes:
41+
* <ul>
42+
* <li>class header (class and superclass declaration)</li>
43+
* <li>arity of type parameters for fields, parameters, local variables</li>
44+
* <li>invalid diamond {@code <>} usage</li>
45+
* </ul>
46+
*/
47+
public class GenericsVisitor extends ClassCodeVisitorSupport {
48+
private final SourceUnit source;
49+
50+
public GenericsVisitor(SourceUnit source) {
51+
this.source = source;
52+
}
53+
54+
protected SourceUnit getSourceUnit() {
55+
return source;
56+
}
57+
58+
@Override
59+
public void visitClass(ClassNode node) {
60+
boolean error = checkWildcard(node);
61+
if (error) return;
62+
boolean isAnon = node instanceof InnerClassNode && ((InnerClassNode) node).isAnonymous();
63+
checkGenericsUsage(node.getUnresolvedSuperClass(false), node.getSuperClass(), isAnon ? true : null);
64+
ClassNode[] interfaces = node.getInterfaces();
65+
for (ClassNode anInterface : interfaces) {
66+
checkGenericsUsage(anInterface, anInterface.redirect());
67+
}
68+
// GRECLIPSE add
69+
visitObjectInitializerStatements(node);
70+
// GRECLIPSE end
71+
node.visitContents(this);
72+
}
73+
74+
@Override
75+
public void visitField(FieldNode node) {
76+
ClassNode type = node.getType();
77+
checkGenericsUsage(type, type.redirect());
78+
super.visitField(node);
79+
}
80+
81+
@Override
82+
public void visitConstructorCallExpression(ConstructorCallExpression call) {
83+
ClassNode type = call.getType();
84+
boolean isAnon = type instanceof InnerClassNode && ((InnerClassNode) type).isAnonymous();
85+
checkGenericsUsage(type, type.redirect(), isAnon);
86+
// GRECLIPSE add
87+
super.visitConstructorCallExpression(call);
88+
// GRECLIPSE end
89+
}
90+
91+
/* GRECLIPSE edit -- GROOVY-10033
92+
@Override
93+
public void visitMethod(MethodNode node) {
94+
Parameter[] parameters = node.getParameters();
95+
for (Parameter param : parameters) {
96+
ClassNode paramType = param.getType();
97+
checkGenericsUsage(paramType, paramType.redirect());
98+
}
99+
ClassNode returnType = node.getReturnType();
100+
checkGenericsUsage(returnType, returnType.redirect());
101+
super.visitMethod(node);
102+
}
103+
*/
104+
@Override
105+
protected void visitConstructorOrMethod(final MethodNode node, final boolean isConstructor) {
106+
for (Parameter p : node.getParameters()) {
107+
checkGenericsUsage(p.getType(), p.getType().redirect());
108+
}
109+
if (!isConstructor) {
110+
checkGenericsUsage(node.getReturnType(), node.getReturnType().redirect());
111+
}
112+
113+
super.visitConstructorOrMethod(node, isConstructor);
114+
}
115+
// GRECLIPSE end
116+
117+
@Override
118+
public void visitDeclarationExpression(DeclarationExpression expression) {
119+
if (expression.isMultipleAssignmentDeclaration()) {
120+
TupleExpression tExpr = expression.getTupleExpression();
121+
for (Expression nextExpr : tExpr.getExpressions()) {
122+
ClassNode declType = nextExpr.getType();
123+
checkGenericsUsage(declType, declType.redirect());
124+
}
125+
} else {
126+
ClassNode declType = expression.getVariableExpression().getType();
127+
checkGenericsUsage(declType, declType.redirect());
128+
}
129+
super.visitDeclarationExpression(expression);
130+
}
131+
132+
// GRECLIPSE add
133+
@Override
134+
public void visitArrayExpression(final ArrayExpression expression) {
135+
checkGenericsUsage(expression.getType(), expression.getType().redirect());
136+
137+
super.visitArrayExpression(expression);
138+
}
139+
140+
@Override
141+
public void visitCastExpression(final CastExpression expression) {
142+
checkGenericsUsage(expression.getType(), expression.getType().redirect());
143+
144+
super.visitCastExpression(expression);
145+
}
146+
// GRECLIPSE end
147+
148+
private boolean checkWildcard(ClassNode cn) {
149+
ClassNode sn = cn.getUnresolvedSuperClass(false);
150+
if (sn == null) return false;
151+
GenericsType[] generics = sn.getGenericsTypes();
152+
if (generics == null) return false;
153+
boolean error = false;
154+
for (GenericsType generic : generics) {
155+
if (generic.isWildcard()) {
156+
addError("A supertype may not specify a wildcard type", sn);
157+
error = true;
158+
}
159+
}
160+
return error;
161+
}
162+
163+
private void checkGenericsUsage(ClassNode n, ClassNode cn) {
164+
/* GRECLIPSE edit
165+
checkGenericsUsage(n, cn, null);
166+
*/
167+
while (n.isArray())
168+
n = n.getComponentType();
169+
checkGenericsUsage(n, n.redirect(), null);
170+
// GRECLIPSE end
171+
}
172+
173+
private void checkGenericsUsage(ClassNode n, ClassNode cn, Boolean isAnonInnerClass) {
174+
if (n.isGenericsPlaceHolder()) return;
175+
GenericsType[] nTypes = n.getGenericsTypes();
176+
GenericsType[] cnTypes = cn.getGenericsTypes();
177+
// raw type usage is always allowed
178+
if (nTypes == null) return;
179+
// you can't parameterize a non-generified type
180+
if (cnTypes == null) {
181+
String message = "The class " + getPrintName(n) + " (supplied with " + plural("type parameter", nTypes.length) +
182+
") refers to the class " + getPrintName(cn) + " which takes no parameters";
183+
if (nTypes.length == 0) {
184+
message += " (invalid Diamond <> usage?)";
185+
}
186+
addError(message, n);
187+
return;
188+
}
189+
// parameterize a type by using all of the parameters only
190+
if (nTypes.length != cnTypes.length) {
191+
if (Boolean.FALSE.equals(isAnonInnerClass) && nTypes.length == 0) {
192+
return; // allow Diamond for non-AIC cases from CCE
193+
}
194+
String message;
195+
if (Boolean.TRUE.equals(isAnonInnerClass) && nTypes.length == 0) {
196+
message = "Cannot use diamond <> with anonymous inner classes";
197+
} else {
198+
message = "The class " + getPrintName(n) + " (supplied with " + plural("type parameter", nTypes.length) +
199+
") refers to the class " + getPrintName(cn) +
200+
" which takes " + plural("parameter", cnTypes.length);
201+
if (nTypes.length == 0) {
202+
message += " (invalid Diamond <> usage?)";
203+
}
204+
}
205+
addError(message, n);
206+
return;
207+
}
208+
for (int i = 0; i < nTypes.length; i++) {
209+
ClassNode nType = nTypes[i].getType();
210+
ClassNode cnType = cnTypes[i].getType();
211+
// check nested type parameters
212+
checkGenericsUsage(nType, nType.redirect());
213+
// check bounds: unbounded wildcard (aka "?") is universal substitute
214+
if (!isUnboundedWildcard(nTypes[i])) {
215+
// check upper bound(s)
216+
ClassNode[] bounds = cnTypes[i].getUpperBounds();
217+
218+
// first can be class or interface
219+
boolean valid = nType.isDerivedFrom(cnType) || ((cnType.isInterface() || Traits.isTrait(cnType)) && nType.implementsInterface(cnType));
220+
221+
// subsequent bounds if present can be interfaces
222+
if (valid && bounds != null && bounds.length > 1) {
223+
for (int j = 1; j < bounds.length; j++) {
224+
ClassNode bound = bounds[j];
225+
if (!nType.implementsInterface(bound)) {
226+
valid = false;
227+
break;
228+
}
229+
}
230+
}
231+
232+
if (!valid) {
233+
addError("The type " + nTypes[i].getName() + " is not a valid substitute for the bounded parameter <" +
234+
getPrintName(cnTypes[i]) + ">", nTypes[i]);
235+
}
236+
}
237+
}
238+
}
239+
240+
private String plural(String orig, int count) {
241+
return "" + count + " " + (count == 1 ? orig : orig + "s");
242+
}
243+
244+
private static String getPrintName(GenericsType gt) {
245+
StringBuilder ret = new StringBuilder(gt.getName());
246+
ClassNode[] upperBounds = gt.getUpperBounds();
247+
ClassNode lowerBound = gt.getLowerBound();
248+
if (upperBounds != null) {
249+
if (upperBounds.length != 1 || !"java.lang.Object".equals(getPrintName(upperBounds[0]))) {
250+
ret.append(" extends ");
251+
for (int i = 0; i < upperBounds.length; i++) {
252+
ret.append(getPrintName(upperBounds[i]));
253+
if (i + 1 < upperBounds.length) ret.append(" & ");
254+
}
255+
}
256+
} else if (lowerBound != null) {
257+
ret.append(" super ").append(getPrintName(lowerBound));
258+
}
259+
return ret.toString();
260+
}
261+
262+
private static String getPrintName(ClassNode cn) {
263+
StringBuilder ret = new StringBuilder(cn.getName());
264+
GenericsType[] gts = cn.getGenericsTypes();
265+
if (gts != null) {
266+
ret.append("<");
267+
for (int i = 0; i < gts.length; i++) {
268+
if (i != 0) ret.append(",");
269+
ret.append(getPrintName(gts[i]));
270+
}
271+
ret.append(">");
272+
}
273+
return ret.toString();
274+
}
275+
}

base/org.codehaus.groovy30/.checkstyle

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
<file-match-pattern match-pattern="groovy/control/CompilationUnit.java" include-pattern="false" />
5555
<file-match-pattern match-pattern="groovy/control/CompilerConfiguration.java" include-pattern="false" />
5656
<file-match-pattern match-pattern="groovy/control/ErrorCollector.java" include-pattern="false" />
57+
<file-match-pattern match-pattern="groovy/control/GenericsVisitor.java" include-pattern="false" />
5758
<file-match-pattern match-pattern="groovy/control/ProcessingUnit.java" include-pattern="false" />
5859
<file-match-pattern match-pattern="groovy/control/ResolveVisitor.java" include-pattern="false" />
5960
<file-match-pattern match-pattern="groovy/control/SourceUnit.java" include-pattern="false" />

0 commit comments

Comments
 (0)