blob: 0231cb54671b86db8d1e9fc3328fabfb60957c04 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.codehaus.groovy.control;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUnboundedWildcard;
/**
* Verify correct usage of generics.
* This includes:
* <ul>
* <li>class header (class and superclass declaration)</li>
* <li>arity of type parameters for fields, parameters, local variables</li>
* <li>invalid diamond {@code <>} usage</li>
* </ul>
*/
public class GenericsVisitor extends ClassCodeVisitorSupport {
private final SourceUnit source;
public GenericsVisitor(SourceUnit source) {
this.source = source;
}
protected SourceUnit getSourceUnit() {
return source;
}
@Override
public void visitClass(ClassNode node) {
boolean error = checkWildcard(node);
if (error) return;
boolean isAnon = node instanceof InnerClassNode && ((InnerClassNode) node).isAnonymous();
checkGenericsUsage(node.getUnresolvedSuperClass(false), node.getSuperClass(), isAnon ? true : null);
ClassNode[] interfaces = node.getInterfaces();
for (ClassNode anInterface : interfaces) {
checkGenericsUsage(anInterface, anInterface.redirect());
}
node.visitContents(this);
}
@Override
public void visitField(FieldNode node) {
ClassNode type = node.getType();
checkGenericsUsage(type, type.redirect());
super.visitField(node);
}
@Override
public void visitConstructorCallExpression(ConstructorCallExpression call) {
ClassNode type = call.getType();
boolean isAnon = type instanceof InnerClassNode && ((InnerClassNode) type).isAnonymous();
checkGenericsUsage(type, type.redirect(), isAnon);
}
@Override
public void visitMethod(MethodNode node) {
Parameter[] parameters = node.getParameters();
for (Parameter param : parameters) {
ClassNode paramType = param.getType();
checkGenericsUsage(paramType, paramType.redirect());
}
ClassNode returnType = node.getReturnType();
checkGenericsUsage(returnType, returnType.redirect());
super.visitMethod(node);
}
@Override
public void visitDeclarationExpression(DeclarationExpression expression) {
if (expression.isMultipleAssignmentDeclaration()) {
TupleExpression tExpr = expression.getTupleExpression();
for (Expression nextExpr : tExpr.getExpressions()) {
ClassNode declType = nextExpr.getType();
checkGenericsUsage(declType, declType.redirect());
}
} else {
ClassNode declType = expression.getVariableExpression().getType();
checkGenericsUsage(declType, declType.redirect());
}
super.visitDeclarationExpression(expression);
}
private boolean checkWildcard(ClassNode cn) {
ClassNode sn = cn.getUnresolvedSuperClass(false);
if (sn == null) return false;
GenericsType[] generics = sn.getGenericsTypes();
if (generics == null) return false;
boolean error = false;
for (GenericsType generic : generics) {
if (generic.isWildcard()) {
addError("A supertype may not specify a wildcard type", sn);
error = true;
}
}
return error;
}
private void checkGenericsUsage(ClassNode n, ClassNode cn) {
checkGenericsUsage(n, cn, null);
}
private void checkGenericsUsage(ClassNode n, ClassNode cn, Boolean isAnonInnerClass) {
if (n.isGenericsPlaceHolder()) return;
GenericsType[] nTypes = n.getGenericsTypes();
GenericsType[] cnTypes = cn.getGenericsTypes();
// raw type usage is always allowed
if (nTypes == null) return;
// you can't parameterize a non-generified type
if (cnTypes == null) {
String message = "The class " + getPrintName(n) + " (supplied with " + plural("type parameter", nTypes.length) +
") refers to the class " + getPrintName(cn) + " which takes no parameters";
if (nTypes.length == 0) {
message += " (invalid Diamond <> usage?)";
}
addError(message, n);
return;
}
// parameterize a type by using all of the parameters only
if (nTypes.length != cnTypes.length) {
if (Boolean.FALSE.equals(isAnonInnerClass) && nTypes.length == 0) {
return; // allow Diamond for non-AIC cases from CCE
}
String message;
if (Boolean.TRUE.equals(isAnonInnerClass) && nTypes.length == 0) {
message = "Cannot use diamond <> with anonymous inner classes";
} else {
message = "The class " + getPrintName(n) + " (supplied with " + plural("type parameter", nTypes.length) +
") refers to the class " + getPrintName(cn) +
" which takes " + plural("parameter", cnTypes.length);
if (nTypes.length == 0) {
message += " (invalid Diamond <> usage?)";
}
}
addError(message, n);
return;
}
for (int i = 0; i < nTypes.length; i++) {
ClassNode nType = nTypes[i].getType();
ClassNode cnType = cnTypes[i].getType();
// check nested type parameters
checkGenericsUsage(nType, nType.redirect());
// check bounds: unbounded wildcard (aka "?") is universal substitute
if (!isUnboundedWildcard(nTypes[i])) {
// check upper bound(s)
ClassNode[] bounds = cnTypes[i].getUpperBounds();
// first can be class or interface
boolean valid = nType.isDerivedFrom(cnType) || (cnType.isInterface() && nType.implementsInterface(cnType));
// subsequent bounds if present can be interfaces
if (valid && bounds != null && bounds.length > 1) {
for (int j = 1; j < bounds.length; j++) {
ClassNode bound = bounds[j];
if (!nType.implementsInterface(bound)) {
valid = false;
break;
}
}
}
if (!valid) {
addError("The type " + nTypes[i].getName() + " is not a valid substitute for the bounded parameter <" +
getPrintName(cnTypes[i]) + ">", nTypes[i]);
}
}
}
}
private String plural(String orig, int count) {
return "" + count + " " + (count == 1 ? orig : orig + "s");
}
private static String getPrintName(GenericsType gt) {
StringBuilder ret = new StringBuilder(gt.getName());
ClassNode[] upperBounds = gt.getUpperBounds();
ClassNode lowerBound = gt.getLowerBound();
if (upperBounds != null) {
if (upperBounds.length != 1 || !"java.lang.Object".equals(getPrintName(upperBounds[0]))) {
ret.append(" extends ");
for (int i = 0; i < upperBounds.length; i++) {
ret.append(getPrintName(upperBounds[i]));
if (i + 1 < upperBounds.length) ret.append(" & ");
}
}
} else if (lowerBound != null) {
ret.append(" super ").append(getPrintName(lowerBound));
}
return ret.toString();
}
private static String getPrintName(ClassNode cn) {
StringBuilder ret = new StringBuilder(cn.getName());
GenericsType[] gts = cn.getGenericsTypes();
if (gts != null) {
ret.append("<");
for (int i = 0; i < gts.length; i++) {
if (i != 0) ret.append(",");
ret.append(getPrintName(gts[i]));
}
ret.append(">");
}
return ret.toString();
}
}