blob: 10de20765ad13fdcaa80fc143d3ce17b6ac5b45b [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.transform.stc;
import org.apache.groovy.util.Maps;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.GenericsType.GenericsTypeName;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.Variable;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ArrayExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.ast.tools.GenericsUtils;
import org.codehaus.groovy.ast.tools.ParameterUtils;
import org.codehaus.groovy.ast.tools.WideningCategories;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.Phases;
import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl;
import org.codehaus.groovy.syntax.Types;
import org.codehaus.groovy.tools.GroovyClass;
import org.codehaus.groovy.transform.trait.Traits;
import org.objectweb.asm.Opcodes;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.regex.Matcher;
import static java.lang.Math.min;
import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
import static org.apache.groovy.ast.tools.ClassNodeUtils.samePackageName;
import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.BigInteger_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Boolean_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Byte_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.CLASS_Type;
import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Character_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Double_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Enum_Type;
import static org.codehaus.groovy.ast.ClassHelper.Float_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.GROOVY_OBJECT_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.GSTRING_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Integer_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Long_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Short_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.char_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.double_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.float_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
import static org.codehaus.groovy.ast.ClassHelper.int_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.isNumberType;
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
import static org.codehaus.groovy.ast.ClassHelper.isSAMType;
import static org.codehaus.groovy.ast.ClassHelper.long_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.make;
import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
import static org.codehaus.groovy.ast.ClassHelper.short_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.void_WRAPPER_TYPE;
import static org.codehaus.groovy.ast.tools.GenericsUtils.getSuperClass;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean;
import static org.codehaus.groovy.syntax.Types.BITWISE_AND;
import static org.codehaus.groovy.syntax.Types.BITWISE_AND_EQUAL;
import static org.codehaus.groovy.syntax.Types.BITWISE_OR;
import static org.codehaus.groovy.syntax.Types.BITWISE_OR_EQUAL;
import static org.codehaus.groovy.syntax.Types.BITWISE_XOR;
import static org.codehaus.groovy.syntax.Types.BITWISE_XOR_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_GREATER_THAN;
import static org.codehaus.groovy.syntax.Types.COMPARE_GREATER_THAN_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_IDENTICAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_LESS_THAN;
import static org.codehaus.groovy.syntax.Types.COMPARE_LESS_THAN_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_IDENTICAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_IN;
import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_INSTANCEOF;
import static org.codehaus.groovy.syntax.Types.COMPARE_TO;
import static org.codehaus.groovy.syntax.Types.DIVIDE;
import static org.codehaus.groovy.syntax.Types.DIVIDE_EQUAL;
import static org.codehaus.groovy.syntax.Types.INTDIV;
import static org.codehaus.groovy.syntax.Types.INTDIV_EQUAL;
import static org.codehaus.groovy.syntax.Types.KEYWORD_IN;
import static org.codehaus.groovy.syntax.Types.KEYWORD_INSTANCEOF;
import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT;
import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT_EQUAL;
import static org.codehaus.groovy.syntax.Types.LEFT_SQUARE_BRACKET;
import static org.codehaus.groovy.syntax.Types.LOGICAL_AND;
import static org.codehaus.groovy.syntax.Types.LOGICAL_OR;
import static org.codehaus.groovy.syntax.Types.MATCH_REGEX;
import static org.codehaus.groovy.syntax.Types.MINUS;
import static org.codehaus.groovy.syntax.Types.MINUS_EQUAL;
import static org.codehaus.groovy.syntax.Types.MOD;
import static org.codehaus.groovy.syntax.Types.MOD_EQUAL;
import static org.codehaus.groovy.syntax.Types.MULTIPLY;
import static org.codehaus.groovy.syntax.Types.MULTIPLY_EQUAL;
import static org.codehaus.groovy.syntax.Types.PLUS;
import static org.codehaus.groovy.syntax.Types.PLUS_EQUAL;
import static org.codehaus.groovy.syntax.Types.POWER;
import static org.codehaus.groovy.syntax.Types.POWER_EQUAL;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_EQUAL;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED_EQUAL;
/**
* Static support methods for {@link StaticTypeCheckingVisitor}.
*/
public abstract class StaticTypeCheckingSupport {
protected static final ClassNode Matcher_TYPE = makeWithoutCaching(Matcher.class);
protected static final ClassNode ArrayList_TYPE = makeWithoutCaching(ArrayList.class);
protected static final ClassNode Collection_TYPE = makeWithoutCaching(Collection.class);
protected static final ClassNode Deprecated_TYPE = makeWithoutCaching(Deprecated.class);
protected static final ExtensionMethodCache EXTENSION_METHOD_CACHE = ExtensionMethodCache.INSTANCE;
protected static final Map<ClassNode, Integer> NUMBER_TYPES = Maps.of(
byte_TYPE, 0,
Byte_TYPE, 0,
short_TYPE, 1,
Short_TYPE, 1,
int_TYPE, 2,
Integer_TYPE, 2,
Long_TYPE, 3,
long_TYPE, 3,
float_TYPE, 4,
Float_TYPE, 4,
double_TYPE, 5,
Double_TYPE, 5
);
protected static final Map<String, Integer> NUMBER_OPS = Maps.of(
"plus", PLUS,
"minus", MINUS,
"multiply", MULTIPLY,
"div", DIVIDE,
"or", BITWISE_OR,
"and", BITWISE_AND,
"xor", BITWISE_XOR,
"mod", MOD,
"intdiv", INTDIV,
"leftShift", LEFT_SHIFT,
"rightShift", RIGHT_SHIFT,
"rightShiftUnsigned", RIGHT_SHIFT_UNSIGNED
);
protected static final ClassNode GSTRING_STRING_CLASSNODE = WideningCategories.lowestUpperBound(
STRING_TYPE,
GSTRING_TYPE
);
/**
* This is for internal use only. When an argument method is null, we cannot determine its type, so
* we use this one as a wildcard.
*/
protected static final ClassNode UNKNOWN_PARAMETER_TYPE = make("<unknown parameter type>");
/**
* This comparator is used when we return the list of methods from DGM which name correspond to a given
* name. As we also lookup for DGM methods of superclasses or interfaces, it may be possible to find
* two methods which have the same name and the same arguments. In that case, we should not add the method
* from superclass or interface otherwise the system won't be able to select the correct method, resulting
* in an ambiguous method selection for similar methods.
*/
protected static final Comparator<MethodNode> DGM_METHOD_NODE_COMPARATOR = new Comparator<MethodNode>() {
public int compare(final MethodNode o1, final MethodNode o2) {
if (o1.getName().equals(o2.getName())) {
Parameter[] o1ps = o1.getParameters();
Parameter[] o2ps = o2.getParameters();
if (o1ps.length == o2ps.length) {
boolean allEqual = true;
for (int i = 0; i < o1ps.length && allEqual; i++) {
allEqual = o1ps[i].getType().equals(o2ps[i].getType());
}
if (allEqual) {
if (o1 instanceof ExtensionMethodNode && o2 instanceof ExtensionMethodNode) {
return compare(((ExtensionMethodNode) o1).getExtensionMethodNode(), ((ExtensionMethodNode) o2).getExtensionMethodNode());
}
return 0;
}
} else {
return o1ps.length - o2ps.length;
}
}
return 1;
}
};
/**
* Returns true for expressions of the form x[...]
*
* @param expression an expression
* @return true for array access expressions
*/
protected static boolean isArrayAccessExpression(Expression expression) {
return expression instanceof BinaryExpression && isArrayOp(((BinaryExpression) expression).getOperation().getType());
}
/**
* Called on method call checks in order to determine if a method call corresponds to the
* idiomatic o.with { ... } structure
*
* @param name name of the method called
* @param callArguments arguments of the method
* @return true if the name is "with" and arguments consist of a single closure
*/
public static boolean isWithCall(final String name, final Expression callArguments) {
boolean isWithCall = "with".equals(name) && callArguments instanceof ArgumentListExpression;
if (isWithCall) {
ArgumentListExpression argList = (ArgumentListExpression) callArguments;
List<Expression> expressions = argList.getExpressions();
isWithCall = expressions.size() == 1 && expressions.get(0) instanceof ClosureExpression;
}
return isWithCall;
}
/**
* Given a variable expression, returns the ultimately accessed variable.
*
* @param ve a variable expression
* @return the target variable
*/
protected static Variable findTargetVariable(VariableExpression ve) {
final Variable accessedVariable = ve.getAccessedVariable() != null ? ve.getAccessedVariable() : ve;
if (accessedVariable != ve) {
if (accessedVariable instanceof VariableExpression)
return findTargetVariable((VariableExpression) accessedVariable);
}
return accessedVariable;
}
/**
* @deprecated Use {@link #findDGMMethodsForClassNode(ClassLoader, ClassNode, String)} instead
*/
@Deprecated
protected static Set<MethodNode> findDGMMethodsForClassNode(ClassNode clazz, String name) {
return findDGMMethodsForClassNode(MetaClassRegistryImpl.class.getClassLoader(), clazz, name);
}
public static Set<MethodNode> findDGMMethodsForClassNode(final ClassLoader loader, ClassNode clazz, String name) {
TreeSet<MethodNode> accumulator = new TreeSet<>(DGM_METHOD_NODE_COMPARATOR);
findDGMMethodsForClassNode(loader, clazz, name, accumulator);
return accumulator;
}
/**
* @deprecated Use {@link #findDGMMethodsForClassNode(ClassLoader, ClassNode, String, TreeSet)} instead
*/
@Deprecated
protected static void findDGMMethodsForClassNode(ClassNode clazz, String name, TreeSet<MethodNode> accumulator) {
findDGMMethodsForClassNode(MetaClassRegistryImpl.class.getClassLoader(), clazz, name, accumulator);
}
protected static void findDGMMethodsForClassNode(final ClassLoader loader, ClassNode clazz, String name, TreeSet<MethodNode> accumulator) {
List<MethodNode> fromDGM = EXTENSION_METHOD_CACHE.get(loader).get(clazz.getName());
if (fromDGM != null) {
for (MethodNode node : fromDGM) {
if (node.getName().equals(name)) accumulator.add(node);
}
}
for (ClassNode node : clazz.getInterfaces()) {
findDGMMethodsForClassNode(loader, node, name, accumulator);
}
if (clazz.isArray()) {
ClassNode componentClass = clazz.getComponentType();
if (!componentClass.equals(OBJECT_TYPE) && !isPrimitiveType(componentClass)) {
if (componentClass.isInterface()) {
findDGMMethodsForClassNode(loader, OBJECT_TYPE.makeArray(), name, accumulator);
} else {
findDGMMethodsForClassNode(loader, componentClass.getSuperClass().makeArray(), name, accumulator);
}
}
}
if (clazz.getSuperClass() != null) {
findDGMMethodsForClassNode(loader, clazz.getSuperClass(), name, accumulator);
} else if (!clazz.equals(OBJECT_TYPE)) {
findDGMMethodsForClassNode(loader, OBJECT_TYPE, name, accumulator);
}
}
/**
* Checks that arguments and parameter types match.
*
* @param params method parameters
* @param args type arguments
* @return -1 if arguments do not match, 0 if arguments are of the exact type and &gt; 0 when one or more argument is
* not of the exact type but still match
*/
public static int allParametersAndArgumentsMatch(Parameter[] params, ClassNode[] args) {
if (params == null) {
params = Parameter.EMPTY_ARRAY;
}
int dist = 0;
if (args.length < params.length) return -1;
// we already know there are at least params.length elements in both arrays
for (int i = 0; i < params.length; i++) {
ClassNode paramType = params[i].getType();
ClassNode argType = args[i];
if (!isAssignableTo(argType, paramType)) return -1;
else {
if (!paramType.equals(argType)) dist += getDistance(argType, paramType);
}
}
return dist;
}
/**
* Checks that arguments and parameter types match, expecting that the number of parameters is strictly greater
* than the number of arguments, allowing possible inclusion of default parameters.
*
* @param params method parameters
* @param args type arguments
* @return -1 if arguments do not match, 0 if arguments are of the exact type and >0 when one or more argument is
* not of the exact type but still match
*/
static int allParametersAndArgumentsMatchWithDefaultParams(Parameter[] params, ClassNode[] args) {
int dist = 0;
ClassNode ptype = null;
// we already know the lengths are equal
for (int i = 0, j = 0; i < params.length; i++) {
Parameter param = params[i];
ClassNode paramType = param.getType();
ClassNode arg = j >= args.length ? null : args[j];
if (arg == null || !isAssignableTo(arg, paramType)) {
if (!param.hasInitialExpression() && (ptype == null || !ptype.equals(paramType))) {
return -1; // no default value
}
// a default value exists, we can skip this param
ptype = null;
} else {
j++;
if (!paramType.equals(arg)) dist += getDistance(arg, paramType);
if (param.hasInitialExpression()) {
ptype = arg;
} else {
ptype = null;
}
}
}
return dist;
}
/**
* Checks that excess arguments match the vararg signature parameter.
*
* @param params
* @param args
* @return -1 if no match, 0 if all arguments matches the vararg type and >0 if one or more vararg argument is
* assignable to the vararg type, but still not an exact match
*/
static int excessArgumentsMatchesVargsParameter(Parameter[] params, ClassNode[] args) {
// we already know parameter length is bigger zero and last is a vargs
// the excess arguments are all put in an array for the vargs call
// so check against the component type
int dist = 0;
ClassNode vargsBase = params[params.length - 1].getType().getComponentType();
for (int i = params.length; i < args.length; i++) {
if (!isAssignableTo(args[i], vargsBase)) return -1;
else dist += getClassDistance(vargsBase, args[i]);
}
return dist;
}
/**
* Checks if the last argument matches the vararg type.
*
* @param params
* @param args
* @return -1 if no match, 0 if the last argument is exactly the vararg type and 1 if of an assignable type
*/
static int lastArgMatchesVarg(Parameter[] params, ClassNode... args) {
if (!isVargs(params)) return -1;
// case length ==0 handled already
// we have now two cases,
// the argument is wrapped in the vargs array or
// the argument is an array that can be used for the vargs part directly
// we test only the wrapping part, since the non wrapping is done already
ClassNode lastParamType = params[params.length - 1].getType();
ClassNode ptype = lastParamType.getComponentType();
ClassNode arg = args[args.length - 1];
if (isNumberType(ptype) && isNumberType(arg) && !ptype.equals(arg)) return -1;
return isAssignableTo(arg, ptype) ? min(getDistance(arg, lastParamType), getDistance(arg, ptype)) : -1;
}
/**
* Checks if a class node is assignable to another. This is used for example in
* assignment checks where you want to verify that the assignment is valid.
*
* @param type
* @param toBeAssignedTo
* @return true if the class node is assignable to the other class node, false otherwise
*/
static boolean isAssignableTo(ClassNode type, ClassNode toBeAssignedTo) {
if (UNKNOWN_PARAMETER_TYPE == type) return true;
if (type == toBeAssignedTo) return true;
if (toBeAssignedTo.redirect() == STRING_TYPE && type.redirect() == GSTRING_TYPE) {
return true;
}
if (isPrimitiveType(toBeAssignedTo)) toBeAssignedTo = getWrapper(toBeAssignedTo);
if (isPrimitiveType(type)) type = getWrapper(type);
if (NUMBER_TYPES.containsKey(type.redirect()) && NUMBER_TYPES.containsKey(toBeAssignedTo.redirect())) {
return NUMBER_TYPES.get(type.redirect()) <= NUMBER_TYPES.get(toBeAssignedTo.redirect());
}
if (type.isArray() && toBeAssignedTo.isArray()) {
return isAssignableTo(type.getComponentType(), toBeAssignedTo.getComponentType());
}
if (type.isDerivedFrom(GSTRING_TYPE) && STRING_TYPE.equals(toBeAssignedTo)) {
return true;
}
if (toBeAssignedTo.isDerivedFrom(GSTRING_TYPE) && STRING_TYPE.equals(type)) {
return true;
}
if (implementsInterfaceOrIsSubclassOf(type, toBeAssignedTo)) {
if (OBJECT_TYPE.equals(toBeAssignedTo)) return true;
if (toBeAssignedTo.isUsingGenerics()) {
// perform additional check on generics
// ? extends toBeAssignedTo
GenericsType gt = GenericsUtils.buildWildcardType(toBeAssignedTo);
return gt.isCompatibleWith(type);
}
return true;
}
//SAM check
if (type.isDerivedFrom(CLOSURE_TYPE) && isSAMType(toBeAssignedTo)) {
return true;
}
return false;
}
static boolean isVargs(Parameter[] params) {
if (params.length == 0) return false;
if (params[params.length - 1].getType().isArray()) return true;
return false;
}
public static boolean isCompareToBoolean(int op) {
return op == COMPARE_GREATER_THAN ||
op == COMPARE_GREATER_THAN_EQUAL ||
op == COMPARE_LESS_THAN ||
op == COMPARE_LESS_THAN_EQUAL;
}
static boolean isArrayOp(int op) {
return op == LEFT_SQUARE_BRACKET;
}
static boolean isBoolIntrinsicOp(int op) {
return op == LOGICAL_AND || op == LOGICAL_OR || op == COMPARE_NOT_IDENTICAL || op == COMPARE_IDENTICAL ||
op == MATCH_REGEX || op == KEYWORD_INSTANCEOF || op == COMPARE_NOT_INSTANCEOF;
}
static boolean isPowerOperator(int op) {
return op == POWER || op == POWER_EQUAL;
}
static String getOperationName(int op) {
switch (op) {
case COMPARE_EQUAL:
case COMPARE_NOT_EQUAL:
// this is only correct in this context here, normally
// we would have to compile against compareTo if available
// but since we don't compile here, this one is enough
return "equals";
case COMPARE_TO:
case COMPARE_GREATER_THAN:
case COMPARE_GREATER_THAN_EQUAL:
case COMPARE_LESS_THAN:
case COMPARE_LESS_THAN_EQUAL:
return "compareTo";
case BITWISE_AND:
case BITWISE_AND_EQUAL:
return "and";
case BITWISE_OR:
case BITWISE_OR_EQUAL:
return "or";
case BITWISE_XOR:
case BITWISE_XOR_EQUAL:
return "xor";
case PLUS:
case PLUS_EQUAL:
return "plus";
case MINUS:
case MINUS_EQUAL:
return "minus";
case MULTIPLY:
case MULTIPLY_EQUAL:
return "multiply";
case DIVIDE:
case DIVIDE_EQUAL:
return "div";
case INTDIV:
case INTDIV_EQUAL:
return "intdiv";
case MOD:
case MOD_EQUAL:
return "mod";
case POWER:
case POWER_EQUAL:
return "power";
case LEFT_SHIFT:
case LEFT_SHIFT_EQUAL:
return "leftShift";
case RIGHT_SHIFT:
case RIGHT_SHIFT_EQUAL:
return "rightShift";
case RIGHT_SHIFT_UNSIGNED:
case RIGHT_SHIFT_UNSIGNED_EQUAL:
return "rightShiftUnsigned";
case KEYWORD_IN:
return "isCase";
case COMPARE_NOT_IN:
return "isNotCase";
default:
return null;
}
}
static boolean isShiftOperation(String name) {
return "leftShift".equals(name) || "rightShift".equals(name) || "rightShiftUnsigned".equals(name);
}
/**
* Returns true for operations that are of the class, that given a common type class for left and right, the
* operation "left op right" will have a result in the same type class In Groovy on numbers that is +,-,* as well as
* their variants with equals.
*/
static boolean isOperationInGroup(int op) {
switch (op) {
case PLUS:
case PLUS_EQUAL:
case MINUS:
case MINUS_EQUAL:
case MULTIPLY:
case MULTIPLY_EQUAL:
return true;
default:
return false;
}
}
static boolean isBitOperator(int op) {
switch (op) {
case BITWISE_OR_EQUAL:
case BITWISE_OR:
case BITWISE_AND_EQUAL:
case BITWISE_AND:
case BITWISE_XOR_EQUAL:
case BITWISE_XOR:
return true;
default:
return false;
}
}
public static boolean isAssignment(int op) {
return Types.isAssignment(op);
}
/**
* Returns true or false depending on whether the right classnode can be assigned to the left classnode. This method
* should not add errors by itself: we let the caller decide what to do if an incompatible assignment is found.
*
* @param left the class to be assigned to
* @param right the assignee class
* @return false if types are incompatible
*/
public static boolean checkCompatibleAssignmentTypes(ClassNode left, ClassNode right) {
return checkCompatibleAssignmentTypes(left, right, null);
}
public static boolean checkCompatibleAssignmentTypes(ClassNode left, ClassNode right, Expression rightExpression) {
return checkCompatibleAssignmentTypes(left, right, rightExpression, true);
}
public static boolean checkCompatibleAssignmentTypes(ClassNode left, ClassNode right, Expression rightExpression, boolean allowConstructorCoercion) {
ClassNode leftRedirect = left.redirect();
ClassNode rightRedirect = right.redirect();
if (leftRedirect == rightRedirect) return true;
if (leftRedirect.isArray() && rightRedirect.isArray()) {
return checkCompatibleAssignmentTypes(leftRedirect.getComponentType(), rightRedirect.getComponentType(), rightExpression, false);
}
if (right == VOID_TYPE || right == void_WRAPPER_TYPE) {
return left == VOID_TYPE || left == void_WRAPPER_TYPE;
}
if ((isNumberType(rightRedirect) || WideningCategories.isNumberCategory(rightRedirect))) {
if (BigDecimal_TYPE == leftRedirect) {
// any number can be assigned to a big decimal
return true;
}
if (BigInteger_TYPE == leftRedirect) {
return WideningCategories.isBigIntCategory(getUnwrapper(rightRedirect)) ||
rightRedirect.isDerivedFrom(BigInteger_TYPE);
}
}
// if rightExpression is null and leftExpression is not a primitive type, it's ok
boolean rightExpressionIsNull = rightExpression instanceof ConstantExpression && ((ConstantExpression) rightExpression).getValue() == null;
if (rightExpressionIsNull && !isPrimitiveType(left)) {
return true;
}
// on an assignment everything that can be done by a GroovyCast is allowed
// anything can be assigned to an Object, String, Boolean
// or Class typed variable
if (isWildcardLeftHandSide(leftRedirect)
&& !(boolean_TYPE.equals(left) && rightExpressionIsNull)) return true;
// char as left expression
if (leftRedirect == char_TYPE && rightRedirect == STRING_TYPE) {
if (rightExpression instanceof ConstantExpression) {
String value = rightExpression.getText();
return value.length() == 1;
}
}
if (leftRedirect == Character_TYPE && (rightRedirect == STRING_TYPE || rightExpressionIsNull)) {
return rightExpressionIsNull || (rightExpression instanceof ConstantExpression && rightExpression.getText().length() == 1);
}
// if left is Enum and right is String or GString we do valueOf
if (leftRedirect.isDerivedFrom(Enum_Type) &&
(rightRedirect == GSTRING_TYPE || rightRedirect == STRING_TYPE)) {
return true;
}
// if right is array, map or collection we try invoking the
// constructor
if (allowConstructorCoercion && isGroovyConstructorCompatible(rightExpression)) {
//TODO: in case of the array we could maybe make a partial check
if (leftRedirect.isArray() && rightRedirect.isArray()) {
return checkCompatibleAssignmentTypes(leftRedirect.getComponentType(), rightRedirect.getComponentType());
} else if (rightRedirect.isArray() && !leftRedirect.isArray()) {
return false;
}
return true;
}
// simple check on being subclass
if (right.isDerivedFrom(left) || (left.isInterface() && right.implementsInterface(left))) return true;
// if left and right are primitives or numbers allow
if (isPrimitiveType(leftRedirect) && isPrimitiveType(rightRedirect)) return true;
if (isNumberType(leftRedirect) && isNumberType(rightRedirect)) return true;
// left is a float/double and right is a BigDecimal
if (WideningCategories.isFloatingCategory(leftRedirect) && BigDecimal_TYPE.equals(rightRedirect)) {
return true;
}
if (GROOVY_OBJECT_TYPE.equals(leftRedirect) && isBeingCompiled(right)) {
return true;
}
if (left.isGenericsPlaceHolder()) {
// GROOVY-7307
GenericsType[] genericsTypes = left.getGenericsTypes();
if (genericsTypes != null && genericsTypes.length == 1) {
// should always be the case, but safe guard is better
return genericsTypes[0].isCompatibleWith(right);
}
}
// GROOVY-7316 : it is an apparently legal thing to allow this. It's not type safe,
// but it is allowed...
return right.isGenericsPlaceHolder();
}
private static boolean isGroovyConstructorCompatible(final Expression rightExpression) {
return rightExpression instanceof ListExpression
|| rightExpression instanceof MapExpression
|| rightExpression instanceof ArrayExpression;
}
/**
* Tells if a class is one of the "accept all" classes as the left hand side of an
* assignment.
*
* @param node the classnode to test
* @return true if it's an Object, String, boolean, Boolean or Class.
*/
public static boolean isWildcardLeftHandSide(final ClassNode node) {
if (OBJECT_TYPE.equals(node) ||
STRING_TYPE.equals(node) ||
boolean_TYPE.equals(node) ||
Boolean_TYPE.equals(node) ||
CLASS_Type.equals(node)) {
return true;
}
return false;
}
public static boolean isBeingCompiled(ClassNode node) {
return node.getCompileUnit() != null;
}
@Deprecated
static boolean checkPossibleLooseOfPrecision(ClassNode left, ClassNode right, Expression rightExpr) {
return checkPossibleLossOfPrecision(left, right, rightExpr);
}
static boolean checkPossibleLossOfPrecision(ClassNode left, ClassNode right, Expression rightExpr) {
if (left == right || left.equals(right)) return false; // identical types
int leftIndex = NUMBER_TYPES.get(left);
int rightIndex = NUMBER_TYPES.get(right);
if (leftIndex >= rightIndex) return false;
// here we must check if the right number is short enough to fit in the left type
if (rightExpr instanceof ConstantExpression) {
Object value = ((ConstantExpression) rightExpr).getValue();
if (!(value instanceof Number)) return true;
Number number = (Number) value;
switch (leftIndex) {
case 0: { // byte
byte val = number.byteValue();
if (number instanceof Short) {
return !Short.valueOf(val).equals(number);
}
if (number instanceof Integer) {
return !Integer.valueOf(val).equals(number);
}
if (number instanceof Long) {
return !Long.valueOf(val).equals(number);
}
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
}
case 1: { // short
short val = number.shortValue();
if (number instanceof Integer) {
return !Integer.valueOf(val).equals(number);
}
if (number instanceof Long) {
return !Long.valueOf(val).equals(number);
}
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
}
case 2: { // integer
int val = number.intValue();
if (number instanceof Long) {
return !Long.valueOf(val).equals(number);
}
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
}
case 3: { // long
long val = number.longValue();
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
}
case 4: { // float
float val = number.floatValue();
return !Double.valueOf(val).equals(number);
}
default: // double
return false; // no possible loose here
}
}
return true; // possible loss of precision
}
static String toMethodParametersString(String methodName, ClassNode... parameters) {
StringBuilder sb = new StringBuilder();
sb.append(methodName).append("(");
if (parameters != null) {
for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
final ClassNode parameter = parameters[i];
sb.append(prettyPrintType(parameter));
if (i < parametersLength - 1) sb.append(", ");
}
}
sb.append(")");
return sb.toString();
}
static String prettyPrintType(ClassNode type) {
if (type.isArray()) {
return prettyPrintType(type.getComponentType()) + "[]";
}
return type.toString(false);
}
public static boolean implementsInterfaceOrIsSubclassOf(ClassNode type, ClassNode superOrInterface) {
boolean result = type.equals(superOrInterface)
|| type.isDerivedFrom(superOrInterface)
|| type.implementsInterface(superOrInterface)
|| type == UNKNOWN_PARAMETER_TYPE;
if (result) {
return true;
}
if (superOrInterface instanceof WideningCategories.LowestUpperBoundClassNode) {
WideningCategories.LowestUpperBoundClassNode cn = (WideningCategories.LowestUpperBoundClassNode) superOrInterface;
result = implementsInterfaceOrIsSubclassOf(type, cn.getSuperClass());
if (result) {
for (ClassNode interfaceNode : cn.getInterfaces()) {
result = type.implementsInterface(interfaceNode);
if (!result) break;
}
}
if (result) return true;
} else if (superOrInterface instanceof UnionTypeClassNode) {
UnionTypeClassNode union = (UnionTypeClassNode) superOrInterface;
for (ClassNode delegate : union.getDelegates()) {
if (implementsInterfaceOrIsSubclassOf(type, delegate)) return true;
}
}
if (type.isArray() && superOrInterface.isArray()) {
return implementsInterfaceOrIsSubclassOf(type.getComponentType(), superOrInterface.getComponentType());
}
if (GROOVY_OBJECT_TYPE.equals(superOrInterface) && !type.isInterface() && isBeingCompiled(type)) {
return true;
}
return false;
}
static int getPrimitiveDistance(ClassNode primA, ClassNode primB) {
return Math.abs(NUMBER_TYPES.get(primA) - NUMBER_TYPES.get(primB));
}
static int getDistance(final ClassNode receiver, final ClassNode compare) {
if (receiver.isArray() && compare.isArray()) {
return getDistance(receiver.getComponentType(), compare.getComponentType());
}
int dist = 0;
ClassNode unwrapReceiver = getUnwrapper(receiver);
ClassNode unwrapCompare = getUnwrapper(compare);
if (isPrimitiveType(unwrapReceiver)
&& isPrimitiveType(unwrapCompare)
&& unwrapReceiver != unwrapCompare) {
dist = getPrimitiveDistance(unwrapReceiver, unwrapCompare);
}
// Add a penalty against boxing or unboxing, to get a resolution similar to JLS 15.12.2
// (http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2).
if (isPrimitiveType(receiver) ^ isPrimitiveType(compare)) {
dist = (dist + 1) << 1;
}
if (unwrapCompare.equals(unwrapReceiver)) return dist;
if (receiver.isArray() && !compare.isArray()) {
// Object[] vs Object
dist += 256;
}
if (receiver == UNKNOWN_PARAMETER_TYPE) {
return dist;
}
ClassNode ref = isPrimitiveType(receiver) && !isPrimitiveType(compare) ? ClassHelper.getWrapper(receiver) : receiver;
while (ref != null) {
if (compare.equals(ref)) {
break;
}
if (compare.isInterface() && ref.implementsInterface(compare)) {
dist += getMaximumInterfaceDistance(ref, compare);
break;
}
ref = ref.getSuperClass();
dist++;
if (ref == null) dist++;
dist = (dist + 1) << 1;
}
return dist;
}
private static int getMaximumInterfaceDistance(ClassNode c, ClassNode interfaceClass) {
// -1 means a mismatch
if (c == null) return -1;
// 0 means a direct match
if (c.equals(interfaceClass)) return 0;
ClassNode[] interfaces = c.getInterfaces();
int max = -1;
for (ClassNode anInterface : interfaces) {
int sub = getMaximumInterfaceDistance(anInterface, interfaceClass);
// we need to keep the -1 to track the mismatch, a +1
// by any means could let it look like a direct match
// we want to add one, because there is an interface between
// the interface we search for and the interface we are in.
if (sub != -1) {
sub += 1;
}
// we are interested in the longest path only
max = Math.max(max, sub);
}
// we do not add one for super classes, only for interfaces
int superClassMax = getMaximumInterfaceDistance(c.getSuperClass(), interfaceClass);
return Math.max(max, superClassMax);
}
/**
* @deprecated Use {@link #findDGMMethodsByNameAndArguments(ClassLoader, ClassNode, String, ClassNode[], List)} instead
*/
@Deprecated
public static List<MethodNode> findDGMMethodsByNameAndArguments(final ClassNode receiver, final String name, final ClassNode[] args) {
return findDGMMethodsByNameAndArguments(MetaClassRegistryImpl.class.getClassLoader(), receiver, name, args);
}
public static List<MethodNode> findDGMMethodsByNameAndArguments(final ClassLoader loader, final ClassNode receiver, final String name, final ClassNode[] args) {
return findDGMMethodsByNameAndArguments(loader, receiver, name, args, new LinkedList<>());
}
/**
* @deprecated Use {@link #findDGMMethodsByNameAndArguments(ClassLoader, ClassNode, String, ClassNode[], List)} instead
*/
@Deprecated
public static List<MethodNode> findDGMMethodsByNameAndArguments(final ClassNode receiver, final String name, final ClassNode[] args, final List<MethodNode> methods) {
return findDGMMethodsByNameAndArguments(MetaClassRegistryImpl.class.getClassLoader(), receiver, name, args, methods);
}
public static List<MethodNode> findDGMMethodsByNameAndArguments(final ClassLoader loader, final ClassNode receiver, final String name, final ClassNode[] args, final List<MethodNode> methods) {
final List<MethodNode> chosen;
methods.addAll(findDGMMethodsForClassNode(loader, receiver, name));
if (methods.isEmpty()) return methods;
chosen = chooseBestMethod(receiver, methods, args);
return chosen;
}
/**
* Returns true if the provided class node, when considered as a receiver of a message or as a parameter,
* is using a placeholder in its generics type. In this case, we're facing unchecked generics and type
* checking is limited (ex: void foo(Set s) { s.keySet() }
*
* @param node the node to test
* @return true if it is using any placeholder in generics types
*/
public static boolean isUsingUncheckedGenerics(ClassNode node) {
if (node.isArray()) return isUsingUncheckedGenerics(node.getComponentType());
if (node.isUsingGenerics()) {
GenericsType[] genericsTypes = node.getGenericsTypes();
if (genericsTypes != null) {
for (GenericsType genericsType : genericsTypes) {
if (genericsType.isPlaceholder()) {
return true;
} else {
if (isUsingUncheckedGenerics(genericsType.getType())) {
return true;
}
}
}
}
} else {
return false;
}
return false;
}
/**
* Given a list of candidate methods, returns the one which best matches the argument types
*
* @param receiver
* @param methods candidate methods
* @param args argument types
* @return the list of methods which best matches the argument types. It is still possible that multiple
* methods match the argument types.
*/
public static List<MethodNode> chooseBestMethod(final ClassNode receiver, Collection<MethodNode> methods, ClassNode... args) {
if (methods.isEmpty()) return Collections.emptyList();
if (isUsingUncheckedGenerics(receiver)) {
ClassNode raw = makeRawType(receiver);
return chooseBestMethod(raw, methods, args);
}
List<MethodNode> bestChoices = new LinkedList<>();
int bestDist = Integer.MAX_VALUE;
Collection<MethodNode> choicesLeft = removeCovariantsAndInterfaceEquivalents(methods);
for (MethodNode candidateNode : choicesLeft) {
ClassNode declaringClassForDistance = candidateNode.getDeclaringClass();
ClassNode actualReceiverForDistance = receiver != null ? receiver : candidateNode.getDeclaringClass();
MethodNode safeNode = candidateNode;
ClassNode[] safeArgs = args;
boolean isExtensionMethodNode = candidateNode instanceof ExtensionMethodNode;
if (isExtensionMethodNode) {
safeArgs = new ClassNode[args.length + 1];
System.arraycopy(args, 0, safeArgs, 1, args.length);
safeArgs[0] = receiver;
safeNode = ((ExtensionMethodNode) candidateNode).getExtensionMethodNode();
}
// todo : corner case
/*
class B extends A {}
Animal foo(A o) {...}
Person foo(B i){...}
B a = new B()
Person p = foo(b)
*/
Map<GenericsType, GenericsType> declaringAndActualGenericsTypeMap = GenericsUtils.makeDeclaringAndActualGenericsTypeMap(declaringClassForDistance, actualReceiverForDistance);
Parameter[] params = makeRawTypes(safeNode.getParameters(), declaringAndActualGenericsTypeMap);
int dist = measureParametersAndArgumentsDistance(params, safeArgs);
if (dist >= 0) {
dist += getClassDistance(declaringClassForDistance, actualReceiverForDistance);
dist += getExtensionDistance(isExtensionMethodNode);
if (dist < bestDist) {
bestChoices.clear();
bestChoices.add(candidateNode);
bestDist = dist;
} else if (dist == bestDist) {
bestChoices.add(candidateNode);
}
}
}
if (bestChoices.size() > 1) {
// GROOVY-6849: prefer extension methods in case of ambiguity
List<MethodNode> onlyExtensionMethods = new LinkedList<>();
for (MethodNode choice : bestChoices) {
if (choice instanceof ExtensionMethodNode) {
onlyExtensionMethods.add(choice);
}
}
if (onlyExtensionMethods.size() == 1) {
return onlyExtensionMethods;
}
}
return bestChoices;
}
private static int measureParametersAndArgumentsDistance(Parameter[] params, ClassNode[] args) {
int dist = -1;
if (params.length == args.length) {
int allPMatch = allParametersAndArgumentsMatch(params, args);
int firstParamDist = firstParametersAndArgumentsMatch(params, args);
int lastArgMatch = isVargs(params) && firstParamDist >= 0 ? lastArgMatchesVarg(params, args) : -1;
if (lastArgMatch >= 0) {
lastArgMatch += getVarargsDistance(params);
}
dist = allPMatch >= 0 ? Math.max(allPMatch, lastArgMatch) : lastArgMatch;
} else if (isVargs(params)) {
dist = firstParametersAndArgumentsMatch(params, args);
if (dist >= 0) {
// varargs methods must not be preferred to methods without varargs
// for example :
// int sum(int x) should be preferred to int sum(int x, int... y)
dist += getVarargsDistance(params);
// there are three case for vargs
// (1) varg part is left out (there's one less argument than there are parameters)
// (2) last argument is put in the vargs array
// that case is handled above already when params and args have the same length
if (params.length < args.length) {
// (3) there is more than one argument for the vargs array
int excessArgumentsDistance = excessArgumentsMatchesVargsParameter(params, args);
if (excessArgumentsDistance < 0) {
dist = -1;
} else {
dist += excessArgumentsDistance;
}
}
}
}
return dist;
}
private static int firstParametersAndArgumentsMatch(Parameter[] params, ClassNode[] safeArgs) {
int dist = 0;
// check first parameters
if (params.length > 0) {
Parameter[] firstParams = new Parameter[params.length - 1];
System.arraycopy(params, 0, firstParams, 0, firstParams.length);
dist = allParametersAndArgumentsMatch(firstParams, safeArgs);
}
return dist;
}
private static int getVarargsDistance(Parameter[] params) {
return 256 - params.length; // ensure exact matches are preferred over vargs
}
private static int getClassDistance(ClassNode declaringClassForDistance, ClassNode actualReceiverForDistance) {
if (actualReceiverForDistance.equals(declaringClassForDistance)) {
return 0;
}
return getDistance(actualReceiverForDistance, declaringClassForDistance);
}
private static int getExtensionDistance(boolean isExtensionMethodNode) {
return isExtensionMethodNode ? 0 : 1;
}
private static Parameter[] makeRawTypes(Parameter[] params, Map<GenericsType, GenericsType> genericsPlaceholderAndTypeMap) {
return Arrays.stream(params).map(param -> {
String name = param.getType().getUnresolvedName();
Optional<GenericsType> value = genericsPlaceholderAndTypeMap.entrySet().stream()
.filter(e -> e.getKey().getName().equals(name)).findFirst().map(Map.Entry::getValue);
ClassNode type = value.map(GenericsType::getType).orElseGet(() -> makeRawType(param.getType()));
return new Parameter(type, param.getName());
}).toArray(Parameter[]::new);
}
private static ClassNode makeRawType(final ClassNode receiver) {
if (receiver.isArray()) {
return makeRawType(receiver.getComponentType()).makeArray();
}
ClassNode raw = receiver.getPlainNodeReference();
raw.setUsingGenerics(false);
raw.setGenericsTypes(null);
return raw;
}
private static Collection<MethodNode> removeCovariantsAndInterfaceEquivalents(Collection<MethodNode> collection) {
if (collection.size() <= 1) return collection;
List<MethodNode> toBeRemoved = new LinkedList<>();
List<MethodNode> list = new LinkedList<>(new LinkedHashSet<>(collection));
for (int i = 0; i < list.size() - 1; i++) {
MethodNode one = list.get(i);
if (toBeRemoved.contains(one)) continue;
for (int j = i + 1; j < list.size(); j++) {
MethodNode two = list.get(j);
if (toBeRemoved.contains(two)) continue;
if (one.getParameters().length == two.getParameters().length) {
if (areOverloadMethodsInSameClass(one, two)) {
if (ParameterUtils.parametersEqual(one.getParameters(), two.getParameters())) {
removeMethodWithSuperReturnType(toBeRemoved, one, two);
} else {
// this is an imperfect solution to determining if two methods are
// equivalent, for example String#compareTo(Object) and String#compareTo(String)
// in that case, Java marks the Object version as synthetic
removeSyntheticMethodIfOne(toBeRemoved, one, two);
}
} else if (areEquivalentInterfaceMethods(one, two)) {
// GROOVY-6970 choose between equivalent interface methods
removeMethodInSuperInterface(toBeRemoved, one, two);
}
}
}
}
if (toBeRemoved.isEmpty()) return list;
List<MethodNode> result = new LinkedList<>(list);
result.removeAll(toBeRemoved);
return result;
}
private static void removeMethodInSuperInterface(List<MethodNode> toBeRemoved, MethodNode one, MethodNode two) {
ClassNode oneDC = one.getDeclaringClass();
ClassNode twoDC = two.getDeclaringClass();
if (oneDC.implementsInterface(twoDC)) {
toBeRemoved.add(two);
} else {
toBeRemoved.add(one);
}
}
private static boolean areEquivalentInterfaceMethods(MethodNode one, MethodNode two) {
return one.getName().equals(two.getName())
&& one.getDeclaringClass().isInterface()
&& two.getDeclaringClass().isInterface()
&& ParameterUtils.parametersEqual(one.getParameters(), two.getParameters());
}
private static void removeSyntheticMethodIfOne(List<MethodNode> toBeRemoved, MethodNode one, MethodNode two) {
if (one.isSynthetic() && !two.isSynthetic()) {
toBeRemoved.add(one);
} else if (two.isSynthetic() && !one.isSynthetic()) {
toBeRemoved.add(two);
}
}
private static void removeMethodWithSuperReturnType(List<MethodNode> toBeRemoved, MethodNode one, MethodNode two) {
ClassNode oneRT = one.getReturnType();
ClassNode twoRT = two.getReturnType();
if (isCovariant(oneRT, twoRT)) {
toBeRemoved.add(two);
} else if (isCovariant(twoRT, oneRT)) {
toBeRemoved.add(one);
}
}
private static boolean isCovariant(ClassNode left, ClassNode right) {
if (left.isArray() && right.isArray()) {
return isCovariant(left.getComponentType(), right.getComponentType());
}
return left.isDerivedFrom(right) || left.implementsInterface(right);
}
private static boolean areOverloadMethodsInSameClass(MethodNode one, MethodNode two) {
return one.getName().equals(two.getName()) && one.getDeclaringClass() == two.getDeclaringClass();
}
/**
* Given a receiver and a method node, parameterize the method arguments using
* available generic type information.
*
* @param receiver the class
* @param m the method
* @return the parameterized arguments
*/
public static Parameter[] parameterizeArguments(final ClassNode receiver, final MethodNode m) {
Map<GenericsTypeName, GenericsType> genericFromReceiver = GenericsUtils.extractPlaceholders(receiver);
Map<GenericsTypeName, GenericsType> contextPlaceholders = extractGenericsParameterMapOfThis(m);
Parameter[] methodParameters = m.getParameters();
Parameter[] params = new Parameter[methodParameters.length];
for (int i = 0; i < methodParameters.length; i++) {
Parameter methodParameter = methodParameters[i];
ClassNode paramType = methodParameter.getType();
params[i] = buildParameter(genericFromReceiver, contextPlaceholders, methodParameter, paramType);
}
return params;
}
/**
* Given a parameter, builds a new parameter for which the known generics placeholders are resolved.
*
* @param genericFromReceiver resolved generics from the receiver of the message
* @param placeholdersFromContext, resolved generics from the method context
* @param methodParameter the method parameter for which we want to resolve generic types
* @param paramType the (unresolved) type of the method parameter
* @return a new parameter with the same name and type as the original one, but with resolved generic types
*/
private static Parameter buildParameter(final Map<GenericsTypeName, GenericsType> genericFromReceiver, final Map<GenericsTypeName, GenericsType> placeholdersFromContext, final Parameter methodParameter, final ClassNode paramType) {
if (genericFromReceiver.isEmpty() && (placeholdersFromContext == null || placeholdersFromContext.isEmpty())) {
return methodParameter;
}
if (paramType.isArray()) {
ClassNode componentType = paramType.getComponentType();
Parameter subMethodParameter = new Parameter(componentType, methodParameter.getName());
Parameter component = buildParameter(genericFromReceiver, placeholdersFromContext, subMethodParameter, componentType);
return new Parameter(component.getType().makeArray(), component.getName());
}
ClassNode resolved = resolveClassNodeGenerics(genericFromReceiver, placeholdersFromContext, paramType);
return new Parameter(resolved, methodParameter.getName());
}
/**
* Returns true if a class node makes use of generic types. If the class node represents an
* array type, then checks if the component type is using generics.
*
* @param cn a class node for which to check if it is using generics
* @return true if the type (or component type) is using generics
*/
public static boolean isUsingGenericsOrIsArrayUsingGenerics(ClassNode cn) {
if (cn.isArray()) {
return isUsingGenericsOrIsArrayUsingGenerics(cn.getComponentType());
}
return (cn.isUsingGenerics() && cn.getGenericsTypes() != null);
}
/**
* Given a generics type representing SomeClass&lt;T,V&gt; and a resolved placeholder map, returns a new generics type
* for which placeholders are resolved recursively.
*/
protected static GenericsType fullyResolve(GenericsType gt, Map<GenericsTypeName, GenericsType> placeholders) {
GenericsType fromMap = placeholders.get(new GenericsTypeName(gt.getName()));
if (gt.isPlaceholder() && fromMap != null) {
gt = fromMap;
}
ClassNode type = fullyResolveType(gt.getType(), placeholders);
ClassNode lowerBound = gt.getLowerBound();
if (lowerBound != null) lowerBound = fullyResolveType(lowerBound, placeholders);
ClassNode[] upperBounds = gt.getUpperBounds();
if (upperBounds != null) {
ClassNode[] copy = new ClassNode[upperBounds.length];
for (int i = 0, upperBoundsLength = upperBounds.length; i < upperBoundsLength; i++) {
final ClassNode upperBound = upperBounds[i];
copy[i] = fullyResolveType(upperBound, placeholders);
}
upperBounds = copy;
}
GenericsType genericsType = new GenericsType(type, upperBounds, lowerBound);
genericsType.setWildcard(gt.isWildcard());
return genericsType;
}
protected static ClassNode fullyResolveType(final ClassNode type, final Map<GenericsTypeName, GenericsType> placeholders) {
if (type.isUsingGenerics() && !type.isGenericsPlaceHolder()) {
GenericsType[] gts = type.getGenericsTypes();
if (gts != null) {
GenericsType[] copy = new GenericsType[gts.length];
for (int i = 0; i < gts.length; i++) {
GenericsType genericsType = gts[i];
if (genericsType.isPlaceholder() && placeholders.containsKey(new GenericsTypeName(genericsType.getName()))) {
copy[i] = placeholders.get(new GenericsTypeName(genericsType.getName()));
} else {
copy[i] = fullyResolve(genericsType, placeholders);
}
}
gts = copy;
}
ClassNode result = type.getPlainNodeReference();
result.setGenericsTypes(gts);
return result;
} else if (type.isUsingGenerics() && OBJECT_TYPE.equals(type) && type.getGenericsTypes() != null) {
// Object<T>
GenericsType genericsType = placeholders.get(new GenericsTypeName(type.getGenericsTypes()[0].getName()));
if (genericsType != null) {
return genericsType.getType();
}
} else if (type.isArray()) {
return fullyResolveType(type.getComponentType(), placeholders).makeArray();
}
return type;
}
/**
* Checks that the parameterized generics of an argument are compatible with the generics of the parameter.
*
* @param parameterType the parameter type of a method
* @param argumentType the type of the argument passed to the method
*/
protected static boolean typeCheckMethodArgumentWithGenerics(ClassNode parameterType, ClassNode argumentType, boolean lastArg) {
if (UNKNOWN_PARAMETER_TYPE == argumentType) {
// called with null
return !isPrimitiveType(parameterType);
}
if (!isAssignableTo(argumentType, parameterType) && !lastArg) {
// incompatible assignment
return false;
}
if (!isAssignableTo(argumentType, parameterType) && lastArg) {
if (parameterType.isArray()) {
if (!isAssignableTo(argumentType, parameterType.getComponentType())) {
return false;
}
} else {
return false;
}
}
if (parameterType.isUsingGenerics() && argumentType.isUsingGenerics()) {
GenericsType gt = GenericsUtils.buildWildcardType(parameterType);
if (!gt.isCompatibleWith(argumentType)) {
boolean samCoercion = isSAMType(parameterType) && argumentType.equals(CLOSURE_TYPE);
if (!samCoercion) return false;
}
} else if (parameterType.isArray() && argumentType.isArray()) {
// verify component type
return typeCheckMethodArgumentWithGenerics(parameterType.getComponentType(), argumentType.getComponentType(), lastArg);
} else if (lastArg && parameterType.isArray()) {
// verify component type, but if we reach that point, the only possibility is that the argument is
// the last one of the call, so we're in the cast of a vargs call
// (otherwise, we face a type checker bug)
return typeCheckMethodArgumentWithGenerics(parameterType.getComponentType(), argumentType, lastArg);
}
return true;
}
static void addMethodLevelDeclaredGenerics(MethodNode method, Map<GenericsTypeName, GenericsType> resolvedPlaceholders) {
ClassNode dummy = OBJECT_TYPE.getPlainNodeReference();
dummy.setGenericsTypes(method.getGenericsTypes());
GenericsUtils.extractPlaceholders(dummy, resolvedPlaceholders);
}
protected static boolean typeCheckMethodsWithGenerics(ClassNode receiver, ClassNode[] arguments, MethodNode candidateMethod) {
if (isUsingUncheckedGenerics(receiver)) {
return true;
}
if (CLASS_Type.equals(receiver)
&& receiver.isUsingGenerics()
&& !candidateMethod.getDeclaringClass().equals(receiver)
&& !(candidateMethod instanceof ExtensionMethodNode)) {
return typeCheckMethodsWithGenerics(receiver.getGenericsTypes()[0].getType(), arguments, candidateMethod);
}
// both candidate method and receiver have generic information so a check is possible
GenericsType[] genericsTypes = candidateMethod.getGenericsTypes();
boolean methodUsesGenerics = (genericsTypes != null && genericsTypes.length > 0);
boolean isExtensionMethod = candidateMethod instanceof ExtensionMethodNode;
if (isExtensionMethod && methodUsesGenerics) {
ClassNode[] dgmArgs = new ClassNode[arguments.length + 1];
dgmArgs[0] = receiver;
System.arraycopy(arguments, 0, dgmArgs, 1, arguments.length);
MethodNode extensionMethodNode = ((ExtensionMethodNode) candidateMethod).getExtensionMethodNode();
return typeCheckMethodsWithGenerics(extensionMethodNode.getDeclaringClass(), dgmArgs, extensionMethodNode, true);
} else {
return typeCheckMethodsWithGenerics(receiver, arguments, candidateMethod, false);
}
}
private static boolean typeCheckMethodsWithGenerics(ClassNode receiver, ClassNode[] arguments, MethodNode candidateMethod, boolean isExtensionMethod) {
boolean failure = false;
// correct receiver for inner class
// we assume the receiver is an instance of the declaring class of the
// candidate method, but findMethod returns also outer class methods
// for that receiver. For now we skip receiver based checks in that case
// TODO: correct generics for when receiver is to be skipped
boolean skipBecauseOfInnerClassNotReceiver = isOuterClassOf(receiver, candidateMethod.getDeclaringClass());
Parameter[] parameters = candidateMethod.getParameters();
Map<GenericsTypeName, GenericsType> classGTs;
if (skipBecauseOfInnerClassNotReceiver) {
classGTs = Collections.emptyMap();
} else {
classGTs = GenericsUtils.extractPlaceholders(receiver);
}
if (parameters.length > arguments.length || parameters.length == 0) {
// this is a limitation that must be removed in a future version
// we cannot check generic type arguments if there are default parameters!
return true;
}
// we have here different generics contexts we have to deal with.
// There is firstly the context given through the class, and the method.
// The method context may hide generics given through the class, but use
// the non-hidden ones.
Map<GenericsTypeName, GenericsType> resolvedMethodGenerics = new HashMap<GenericsTypeName, GenericsType>();
if (!skipBecauseOfInnerClassNotReceiver) {
addMethodLevelDeclaredGenerics(candidateMethod, resolvedMethodGenerics);
}
// so first we remove hidden generics
for (GenericsTypeName key : resolvedMethodGenerics.keySet()) classGTs.remove(key);
// then we use the remaining information to refine the given generics
applyGenericsConnections(classGTs, resolvedMethodGenerics);
// and then start our checks with the receiver
if (!skipBecauseOfInnerClassNotReceiver) {
failure |= inferenceCheck(Collections.emptySet(), resolvedMethodGenerics, candidateMethod.getDeclaringClass(), receiver, false);
}
// the outside context parts till now define placeholder we are not allowed to
// generalize, thus we save that for later use...
// extension methods are special, since they set the receiver as
// first parameter. While we normally allow generalization for the first
// parameter, in case of an extension method we must not.
Set<GenericsTypeName> fixedGenericsPlaceHolders = extractResolvedPlaceHolders(resolvedMethodGenerics);
for (int i = 0; i < arguments.length; i++) {
int pindex = min(i, parameters.length - 1);
ClassNode wrappedArgument = arguments[i];
ClassNode type = parameters[pindex].getOriginType();
failure |= inferenceCheck(fixedGenericsPlaceHolders, resolvedMethodGenerics, type, wrappedArgument, i >= parameters.length - 1);
// set real fixed generics for extension methods
if (isExtensionMethod && i == 0)
fixedGenericsPlaceHolders = extractResolvedPlaceHolders(resolvedMethodGenerics);
}
return !failure;
}
private static boolean isOuterClassOf(ClassNode receiver, ClassNode type) {
if (implementsInterfaceOrIsSubclassOf(receiver, type)) return false;
return true;
}
private static Set<GenericsTypeName> extractResolvedPlaceHolders(Map<GenericsTypeName, GenericsType> resolvedMethodGenerics) {
if (resolvedMethodGenerics.isEmpty()) return Collections.emptySet();
Set<GenericsTypeName> result = new HashSet<>();
for (Map.Entry<GenericsTypeName, GenericsType> entry : resolvedMethodGenerics.entrySet()) {
GenericsType value = entry.getValue();
if (value.isPlaceholder()) continue;
result.add(entry.getKey());
}
return result;
}
private static boolean inferenceCheck(Set<GenericsTypeName> fixedGenericsPlaceHolders, Map<GenericsTypeName, GenericsType> resolvedMethodGenerics, ClassNode type, ClassNode wrappedArgument, boolean lastArg) {
Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
if (isPrimitiveType(wrappedArgument)) wrappedArgument = getWrapper(wrappedArgument);
if (lastArg &&
type.isArray() && type.getComponentType().isGenericsPlaceHolder() &&
!wrappedArgument.isArray() && wrappedArgument.isGenericsPlaceHolder()) {
// GROOVY-8090 handle generics varargs, e.g. `U x = ...; Arrays.asList(x)`
// we should connect the type of vararg(e.g. T is the type of T...) to the argument type
type = type.getComponentType();
}
// the context we compare with in the end is the one of the callsite
// so far we specified the context of the method declaration only
// thus for each argument, we try to find the connected generics first
extractGenericsConnections(connections, wrappedArgument, type);
// each found connection must comply with already found connections
boolean failure = !compatibleConnections(connections, resolvedMethodGenerics, fixedGenericsPlaceHolders);
// and then apply the found information to refine the method level
// information. This way the method level information slowly turns
// into information for the callsite
applyGenericsConnections(connections, resolvedMethodGenerics);
// since it is possible that the callsite uses some generics as well,
// we may have to add additional elements here
addMissingEntries(connections, resolvedMethodGenerics);
// to finally see if the parameter and the argument fit together,
// we use the provided information to transform the parameter
// into something that can exist in the callsite context
type = applyGenericsContext(resolvedMethodGenerics, type);
// there of course transformed parameter type and argument must fit
failure |= !typeCheckMethodArgumentWithGenerics(type, wrappedArgument, lastArg);
return failure;
}
private static GenericsType buildWildcardType(GenericsType origin) {
ClassNode lowerBound = origin.getType().getPlainNodeReference();
if (hasNonTrivialBounds(origin)) {
lowerBound.setGenericsTypes(new GenericsType[]{origin});
}
ClassNode base = makeWithoutCaching("?");
GenericsType gt = new GenericsType(base, null, lowerBound);
gt.setWildcard(true);
return gt;
}
private static boolean compatibleConnections(Map<GenericsTypeName, GenericsType> connections, Map<GenericsTypeName, GenericsType> resolvedMethodGenerics, Set<GenericsTypeName> fixedGenericsPlaceHolders) {
for (Map.Entry<GenericsTypeName, GenericsType> entry : connections.entrySet()) {
GenericsType resolved = resolvedMethodGenerics.get(entry.getKey());
if (resolved == null) continue;
GenericsType connection = entry.getValue();
if (connection.isPlaceholder() && !hasNonTrivialBounds(connection)) {
continue;
}
if (!compatibleConnection(resolved, connection)) {
if (!(resolved.isPlaceholder() || resolved.isWildcard()) &&
!fixedGenericsPlaceHolders.contains(entry.getKey()) &&
compatibleConnection(connection, resolved)) {
// we did for example find T=String and now check against
// T=Object, which fails the first compatibleConnection check
// but since T=Object works for both, the second one will pass
// and we need to change the type for T to the more general one
resolvedMethodGenerics.put(entry.getKey(), connection);
} else {
return false;
}
}
}
return true;
}
private static boolean compatibleConnection(GenericsType resolved, GenericsType connection) {
GenericsType gt = connection;
if (!connection.isWildcard()) gt = buildWildcardType(connection);
if (resolved.isPlaceholder() && resolved.getUpperBounds() != null &&
resolved.getUpperBounds().length == 1 && !resolved.getUpperBounds()[0].isGenericsPlaceHolder() &&
resolved.getUpperBounds()[0].getName().equals("java.lang.Object")) {
return true;
}
ClassNode compareNode;
if (hasNonTrivialBounds(resolved)) {
compareNode = getCombinedBoundType(resolved);
compareNode = compareNode.redirect().getPlainNodeReference();
} else {
if (!resolved.isPlaceholder()) {
compareNode = resolved.getType().getPlainNodeReference();
} else {
return true;
}
}
return gt.isCompatibleWith(compareNode);
}
private static void addMissingEntries(Map<GenericsTypeName, GenericsType> connections, Map<GenericsTypeName, GenericsType> resolved) {
for (Map.Entry<GenericsTypeName, GenericsType> entry : connections.entrySet()) {
if (resolved.containsKey(entry.getKey())) continue;
GenericsType gt = entry.getValue();
ClassNode cn = gt.getType();
if (cn.redirect() == UNKNOWN_PARAMETER_TYPE) continue;
resolved.put(entry.getKey(), gt);
}
}
public static ClassNode resolveClassNodeGenerics(Map<GenericsTypeName, GenericsType> resolvedPlaceholders, final Map<GenericsTypeName, GenericsType> placeholdersFromContext, ClassNode currentType) {
ClassNode target = currentType.redirect();
resolvedPlaceholders = new HashMap<>(resolvedPlaceholders);
applyContextGenerics(resolvedPlaceholders, placeholdersFromContext);
Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
extractGenericsConnections(connections, currentType, target);
applyGenericsConnections(connections, resolvedPlaceholders);
currentType = applyGenericsContext(resolvedPlaceholders, currentType);
return currentType;
}
static void applyGenericsConnections(
Map<GenericsTypeName, GenericsType> connections,
Map<GenericsTypeName, GenericsType> resolvedPlaceholders
) {
if (connections == null) return;
int count = 0;
while (count < 10000) {
count += 1;
boolean checkForMorePlaceHolders = false;
for (Map.Entry<GenericsTypeName, GenericsType> entry : resolvedPlaceholders.entrySet()) {
GenericsTypeName name = entry.getKey();
GenericsType replacement = connections.get(name);
if (replacement == null) {
GenericsType value = entry.getValue();
GenericsType newValue = applyGenericsContext(connections, value);
entry.setValue(newValue);
checkForMorePlaceHolders = checkForMorePlaceHolders || !equalIncludingGenerics(value, newValue);
continue;
}
GenericsType original = entry.getValue();
if (!original.isWildcard() && !original.isPlaceholder()) {
continue;
}
boolean placeholderReplacement = replacement.isPlaceholder();
if (placeholderReplacement) {
GenericsType connectedType = resolvedPlaceholders.get(name);
if (replacement == connectedType) continue;
}
// GROOVY-6787: Don't override the original if the replacement placeholder doesn't respect the bounds,
// otherwise the original bounds are lost which can result in accepting an incompatible type as an
// argument, for example.
ClassNode replacementType = extractType(replacement);
if (original.isCompatibleWith(replacementType)) {
entry.setValue(replacement);
if (placeholderReplacement) {
checkForMorePlaceHolders = checkForMorePlaceHolders || !equalIncludingGenerics(original, replacement);
}
}
}
if (!checkForMorePlaceHolders) break;
}
if (count >= 10000)
throw new GroovyBugError("unable to handle generics in " + resolvedPlaceholders + " with connections " + connections);
}
private static ClassNode extractType(GenericsType gt) {
if (!gt.isPlaceholder()) {
return gt.getType();
}
// For a placeholder, a type based on the generics type is used for the compatibility check, to match on
// the actual bounds and not the name of the placeholder.
ClassNode replacementType = OBJECT_TYPE;
if (gt.getType().getGenericsTypes() != null) {
GenericsType realGt = gt.getType().getGenericsTypes()[0];
if (realGt.getLowerBound() != null) {
replacementType = realGt.getLowerBound();
} else if (realGt.getUpperBounds() != null && realGt.getUpperBounds().length > 0) {
replacementType = realGt.getUpperBounds()[0];
}
}
return replacementType;
}
private static boolean equalIncludingGenerics(GenericsType orig, GenericsType copy) {
if (orig == copy) return true;
if (orig.isPlaceholder() != copy.isPlaceholder()) return false;
if (orig.isWildcard() != copy.isWildcard()) return false;
if (!equalIncludingGenerics(orig.getType(), copy.getType())) return false;
ClassNode lower1 = orig.getLowerBound();
ClassNode lower2 = copy.getLowerBound();
if ((lower1 == null) ^ (lower2 == null)) return false;
if (lower1 != lower2) {
if (!equalIncludingGenerics(lower1, lower2)) return false;
}
ClassNode[] upper1 = orig.getUpperBounds();
ClassNode[] upper2 = copy.getUpperBounds();
if ((upper1 == null) ^ (upper2 == null)) return false;
if (upper1 != upper2) {
if (upper1.length != upper2.length) return false;
for (int i = 0; i < upper1.length; i++) {
if (!equalIncludingGenerics(upper1[i], upper2[i])) return false;
}
}
return true;
}
private static boolean equalIncludingGenerics(ClassNode orig, ClassNode copy) {
if (orig == copy) return true;
if (orig.isGenericsPlaceHolder() != copy.isGenericsPlaceHolder()) return false;
if (!orig.equals(copy)) return false;
GenericsType[] gt1 = orig.getGenericsTypes();
GenericsType[] gt2 = orig.getGenericsTypes();
if ((gt1 == null) ^ (gt2 == null)) return false;
if (gt1 != gt2) {
if (gt1.length != gt2.length) return false;
for (int i = 0; i < gt1.length; i++) {
if (!equalIncludingGenerics(gt1[i], gt2[i])) return false;
}
}
return true;
}
/**
* use supplied type to make a connection from usage to declaration
* The method operates in two modes.
* * For type !instanceof target a structural compare will be done
* (for example Dummy&lt;T&gt; and List&lt;R&gt; to get T=R)
* * If type equals target, a structural match is done as well
* (for example Collection&lt;U&gt; and Collection&lt;E&gt; to get U=E)
* * otherwise we climb the hierarchy to find a case of type equals target
* to then execute the structural match, while applying possibly existing
* generics contexts on the way (for example for IntRange and Collection&lt;E&gt;
* to get E=Integer, since IntRange is an AbstractList&lt;Integer&gt;)
* Should the target not have any generics this method does nothing.
*/
static void extractGenericsConnections(Map<GenericsTypeName, GenericsType> connections, ClassNode type, ClassNode target) {
if (target == null || type == target || !isUsingGenericsOrIsArrayUsingGenerics(target)) return;
if (type == null || type == UNKNOWN_PARAMETER_TYPE) return;
if (type.isArray() && target.isArray()) {
extractGenericsConnections(connections, type.getComponentType(), target.getComponentType());
} else if (target.isGenericsPlaceHolder() || type.equals(target) || !implementsInterfaceOrIsSubclassOf(type, target)) {
// structural match route
if (target.isGenericsPlaceHolder()) {
connections.put(new GenericsTypeName(target.getGenericsTypes()[0].getName()), new GenericsType(type));
} else {
extractGenericsConnections(connections, type.getGenericsTypes(), target.getGenericsTypes());
}
} else {
// have first to find matching super class or interface
ClassNode superClass = getSuperClass(type, target);
if (superClass != null) {
ClassNode corrected = getCorrectedClassNode(type, superClass, true);
extractGenericsConnections(connections, corrected, target);
} else {
// if we reach here, we have an unhandled case
throw new GroovyBugError("The type " + type + " seems not to normally extend " + target + ". Sorry, I cannot handle this.");
}
}
}
public static ClassNode getCorrectedClassNode(ClassNode type, ClassNode superClass, boolean handlingGenerics) {
ClassNode corrected;
if (handlingGenerics && missesGenericsTypes(type)) {
corrected = superClass.getPlainNodeReference();
} else {
corrected = GenericsUtils.correctToGenericsSpecRecurse(GenericsUtils.createGenericsSpec(type), superClass);
}
return corrected;
}
private static void extractGenericsConnections(Map<GenericsTypeName, GenericsType> connections, GenericsType[] usage, GenericsType[] declaration) {
// if declaration does not provide generics, there is no connection to make
if (usage == null || declaration == null || declaration.length == 0) return;
if (usage.length != declaration.length) return;
// both have generics
for (int i = 0; i < usage.length; i++) {
GenericsType ui = usage[i];
GenericsType di = declaration[i];
if (di.isPlaceholder()) {
connections.put(new GenericsTypeName(di.getName()), ui);
} else if (di.isWildcard()) {
if (ui.isWildcard()) {
extractGenericsConnections(connections, ui.getLowerBound(), di.getLowerBound());
extractGenericsConnections(connections, ui.getUpperBounds(), di.getUpperBounds());
} else {
ClassNode cu = ui.getType();
extractGenericsConnections(connections, cu, di.getLowerBound());
ClassNode[] upperBounds = di.getUpperBounds();
if (upperBounds != null) {
for (ClassNode cn : upperBounds) {
extractGenericsConnections(connections, cu, cn);
}
}
}
} else {
extractGenericsConnections(connections, ui.getType(), di.getType());
}
}
}
private static void extractGenericsConnections(Map<GenericsTypeName, GenericsType> connections, ClassNode[] usage, ClassNode[] declaration) {
if (usage == null || declaration == null || declaration.length == 0) return;
// both have generics
for (int i = 0; i < usage.length; i++) {
ClassNode ui = usage[i];
ClassNode di = declaration[i];
if (di.isGenericsPlaceHolder()) {
GenericsType gt = new GenericsType(di);
gt.setPlaceholder(di.isGenericsPlaceHolder());
connections.put(new GenericsTypeName(di.getGenericsTypes()[0].getName()), gt);
} else if (di.isUsingGenerics()) {
extractGenericsConnections(connections, ui.getGenericsTypes(), di.getGenericsTypes());
}
}
}
static GenericsType[] getGenericsWithoutArray(ClassNode type) {
if (type.isArray()) return getGenericsWithoutArray(type.getComponentType());
return type.getGenericsTypes();
}
static Map<GenericsTypeName, GenericsType> applyGenericsContextToParameterClass(
Map<GenericsTypeName, GenericsType> spec, ClassNode parameterUsage
) {
GenericsType[] gts = parameterUsage.getGenericsTypes();
if (gts == null) return Collections.emptyMap();
GenericsType[] newGTs = applyGenericsContext(spec, gts);
ClassNode newTarget = parameterUsage.redirect().getPlainNodeReference();
newTarget.setGenericsTypes(newGTs);
return GenericsUtils.extractPlaceholders(newTarget);
}
private static GenericsType[] applyGenericsContext(
Map<GenericsTypeName, GenericsType> spec, GenericsType[] gts
) {
if (gts == null) return null;
GenericsType[] newGTs = new GenericsType[gts.length];
for (int i = 0; i < gts.length; i++) {
GenericsType gt = gts[i];
newGTs[i] = applyGenericsContext(spec, gt);
}
return newGTs;
}
private static GenericsType applyGenericsContext(Map<GenericsTypeName, GenericsType> spec, GenericsType gt) {
if (gt.isPlaceholder()) {
GenericsTypeName name = new GenericsTypeName(gt.getName());
GenericsType specType = spec.get(name);
if (specType != null) return specType;
if (hasNonTrivialBounds(gt)) {
GenericsType newGT = new GenericsType(gt.getType(), applyGenericsContext(spec, gt.getUpperBounds()), applyGenericsContext(spec, gt.getLowerBound()));
newGT.setPlaceholder(true);
return newGT;
}
return gt;
} else if (gt.isWildcard()) {
GenericsType newGT = new GenericsType(gt.getType(), applyGenericsContext(spec, gt.getUpperBounds()), applyGenericsContext(spec, gt.getLowerBound()));
newGT.setWildcard(true);
return newGT;
}
ClassNode type = gt.getType();
ClassNode newType;
if (type.isArray()) {
newType = applyGenericsContext(spec, type.getComponentType()).makeArray();
} else {
if (type.getGenericsTypes()==null) return gt;
newType = type.getPlainNodeReference();
newType.setGenericsPlaceHolder(type.isGenericsPlaceHolder());
newType.setGenericsTypes(applyGenericsContext(spec, type.getGenericsTypes()));
}
return new GenericsType(newType);
}
private static boolean hasNonTrivialBounds(GenericsType gt) {
ClassNode[] upperBounds = gt.getUpperBounds();
return gt.getLowerBound() != null || gt.isWildcard() ||
(upperBounds != null && (
upperBounds.length != 1
|| upperBounds[0].isGenericsPlaceHolder()
|| !OBJECT_TYPE.equals(upperBounds[0])));
}
private static ClassNode[] applyGenericsContext(
Map<GenericsTypeName, GenericsType> spec, ClassNode[] bounds
) {
if (bounds == null) return null;
ClassNode[] newBounds = new ClassNode[bounds.length];
for (int i = 0; i < bounds.length; i++) {
newBounds[i] = applyGenericsContext(spec, bounds[i]);
}
return newBounds;
}
static ClassNode applyGenericsContext(
Map<GenericsTypeName, GenericsType> spec, ClassNode bound
) {
if (bound == null) return null;
if (bound.isArray()) {
return applyGenericsContext(spec, bound.getComponentType()).makeArray();
}
if (!bound.isUsingGenerics()) return bound;
ClassNode newBound = bound.getPlainNodeReference();
newBound.setGenericsTypes(applyGenericsContext(spec, bound.getGenericsTypes()));
if (bound.isGenericsPlaceHolder()) {
GenericsType[] gt = newBound.getGenericsTypes();
boolean hasBounds = hasNonTrivialBounds(gt[0]);
if (hasBounds || !gt[0].isPlaceholder()) return getCombinedBoundType(gt[0]);
String placeHolderName = newBound.getGenericsTypes()[0].getName();
if (!placeHolderName.equals(newBound.getUnresolvedName())) {
// we should produce a clean placeholder ClassNode here
ClassNode clean = make(placeHolderName);
clean.setGenericsTypes(newBound.getGenericsTypes());
clean.setRedirect(newBound);
newBound = clean;
}
newBound.setGenericsPlaceHolder(true);
}
return newBound;
}
private static ClassNode getCombinedBoundType(GenericsType genericsType) {
//TODO: this method should really return some kind of meta ClassNode
// representing the combination of all bounds. The code here, just picks
// something out to be able to proceed and is not actually correct
if (hasNonTrivialBounds(genericsType)) {
if (genericsType.getLowerBound() != null) return genericsType.getLowerBound();
if (genericsType.getUpperBounds() != null) return genericsType.getUpperBounds()[0];
}
return genericsType.getType();
}
private static void applyContextGenerics(Map<GenericsTypeName, GenericsType> resolvedPlaceholders, Map<GenericsTypeName, GenericsType> placeholdersFromContext) {
if (placeholdersFromContext == null) return;
for (Map.Entry<GenericsTypeName, GenericsType> entry : resolvedPlaceholders.entrySet()) {
GenericsType gt = entry.getValue();
if (gt.isPlaceholder()) {
GenericsTypeName name = new GenericsTypeName(gt.getName());
GenericsType outer = placeholdersFromContext.get(name);
if (outer == null) continue;
entry.setValue(outer);
}
}
}
private static Map<GenericsTypeName, GenericsType> getGenericsParameterMapOfThis(ClassNode cn) {
if (cn == null) return null;
Map<GenericsTypeName, GenericsType> map = null;
if (cn.getEnclosingMethod() != null) {
map = extractGenericsParameterMapOfThis(cn.getEnclosingMethod());
} else if (cn.getOuterClass() != null) {
map = getGenericsParameterMapOfThis(cn.getOuterClass());
}
map = mergeGenerics(map, cn.getGenericsTypes());
return map;
}
/**
* Apply the bounds from the declared type when the using type simply declares a parameter as an unbounded wildcard.
*
* @param type A parameterized type
* @return A parameterized type with more precise wildcards
*/
static ClassNode boundUnboundedWildcards(ClassNode type) {
if (type.isArray()) {
return boundUnboundedWildcards(type.getComponentType()).makeArray();
}
ClassNode target = type.redirect();
if (target == null || type == target || !isUsingGenericsOrIsArrayUsingGenerics(target)) return type;
ClassNode newType = type.getPlainNodeReference();
newType.setGenericsPlaceHolder(type.isGenericsPlaceHolder());
newType.setGenericsTypes(boundUnboundedWildcards(type.getGenericsTypes(), target.getGenericsTypes()));
return newType;
}
private static GenericsType[] boundUnboundedWildcards(GenericsType[] usage, GenericsType[] declaration) {
GenericsType[] newGts = new GenericsType[usage.length];
for (int i = 0; i < usage.length; i++) {
newGts[i] = boundUnboundedWildcard(usage[i], declaration[i]);
}
return newGts;
}
private static GenericsType boundUnboundedWildcard(GenericsType gt, GenericsType spec) {
if (isUnboundedWildcard(gt)) {
ClassNode base = makeWithoutCaching("?");
// The bounds on the declared type are at least as good as the ones on an unbounded wildcard, since it has
// none!
GenericsType newGt = new GenericsType(base, spec.getUpperBounds(), spec.getLowerBound());
newGt.setWildcard(true);
return newGt;
}
return gt;
}
public static boolean isUnboundedWildcard(GenericsType gt) {
if (gt.isWildcard() && gt.getLowerBound() == null) {
ClassNode[] upperBounds = gt.getUpperBounds();
return upperBounds == null ||
upperBounds.length == 0 ||
(upperBounds.length == 1 && OBJECT_TYPE.equals(upperBounds[0]));
}
return false;
}
static Map<GenericsTypeName, GenericsType> extractGenericsParameterMapOfThis(MethodNode mn) {
if (mn == null) return null;
Map<GenericsTypeName, GenericsType> map;
if (mn.isStatic()) {
map = new HashMap<>();
} else {
map = getGenericsParameterMapOfThis(mn.getDeclaringClass());
}
return mergeGenerics(map, mn.getGenericsTypes());
}
private static Map<GenericsTypeName, GenericsType> mergeGenerics(Map<GenericsTypeName, GenericsType> current, GenericsType[] newGenerics) {
if (newGenerics == null || newGenerics.length == 0) return current;
if (current == null) current = new HashMap<>();
for (GenericsType gt : newGenerics) {
if (!gt.isPlaceholder()) continue;
GenericsTypeName name = new GenericsTypeName(gt.getName());
if (!current.containsKey(name)) current.put(name, gt);
}
return current;
}
/**
* Filter methods according to visibility
*
* @param methodNodeList method nodes to filter
* @param enclosingClassNode the enclosing class
* @return filtered method nodes
* @since 3.0.0
*/
public static List<MethodNode> filterMethodsByVisibility(List<MethodNode> methodNodeList, ClassNode enclosingClassNode) {
if (!asBoolean(methodNodeList)) {
return StaticTypeCheckingVisitor.EMPTY_METHODNODE_LIST;
}
List<MethodNode> result = new LinkedList<>();
boolean isEnclosingInnerClass = enclosingClassNode instanceof InnerClassNode;
List<ClassNode> outerClasses = enclosingClassNode.getOuterClasses();
outer:
for (MethodNode methodNode : methodNodeList) {
if (methodNode instanceof ExtensionMethodNode) {
result.add(methodNode);
continue;
}
ClassNode declaringClass = methodNode.getDeclaringClass();
if (isEnclosingInnerClass) {
for (ClassNode outerClass : outerClasses) {
if (outerClass.isDerivedFrom(declaringClass)) {
if (outerClass.equals(declaringClass)) {
result.add(methodNode);
continue outer;
} else {
if (methodNode.isPublic() || methodNode.isProtected()) {
result.add(methodNode);
continue outer;
}
}
}
}
}
if (declaringClass instanceof InnerClassNode) {
if (declaringClass.getOuterClasses().contains(enclosingClassNode)) {
result.add(methodNode);
continue;
}
}
if (methodNode.isPrivate() && !enclosingClassNode.equals(declaringClass)) {
continue;
}
if (methodNode.isProtected()
&& !enclosingClassNode.isDerivedFrom(declaringClass)
&& !samePackageName(enclosingClassNode, declaringClass)) {
continue;
}
if (methodNode.isPackageScope() && !samePackageName(enclosingClassNode, declaringClass)) {
continue;
}
result.add(methodNode);
}
return result;
}
/**
* A DGM-like method which adds support for method calls which are handled
* specifically by the Groovy compiler.
*/
public static class ObjectArrayStaticTypesHelper {
public static <T> T getAt(T[] arr, int index) {
return null == arr ? null : arr[index];
}
public static <T, U extends T> void putAt(T[] arr, int index, U object) {
if (null == arr) {
return;
}
arr[index] = object;
}
}
public static class BooleanArrayStaticTypesHelper {
public static Boolean getAt(boolean[] arr, int index) {
return null == arr ? null : arr[index];
}
public static void putAt(boolean[] arr, int index, boolean object) {
if (null == arr) {
return;
}
arr[index] = object;
}
}
public static class CharArrayStaticTypesHelper {
public static Character getAt(char[] arr, int index) {
return null == arr ? null : arr[index];
}
public static void putAt(char[] arr, int index, char object) {
if (null == arr) {
return;
}
arr[index] = object;
}
}
public static class ByteArrayStaticTypesHelper {
public static Byte getAt(byte[] arr, int index) {
return null == arr ? null : arr[index];
}
public static void putAt(byte[] arr, int index, byte object) {
if (null == arr) {
return;
}
arr[index] = object;
}
}
public static class ShortArrayStaticTypesHelper {
public static Short getAt(short[] arr, int index) {
return null == arr ? null : arr[index];
}
public static void putAt(short[] arr, int index, short object) {
if (null == arr) {
return;
}
arr[index] = object;
}
}
public static class IntArrayStaticTypesHelper {
public static Integer getAt(int[] arr, int index) {
return null == arr ? null : arr[index];
}
public static void putAt(int[] arr, int index, int object) {
if (null == arr) {
return;
}
arr[index] = object;
}
}
public static class LongArrayStaticTypesHelper {
public static Long getAt(long[] arr, int index) {
return null == arr ? null : arr[index];
}
public static void putAt(long[] arr, int index, long object) {
if (null == arr) {
return;
}
arr[index] = object;
}
}
public static class FloatArrayStaticTypesHelper {
public static Float getAt(float[] arr, int index) {
return null == arr ? null : arr[index];
}
public static void putAt(float[] arr, int index, float object) {
if (null == arr) {
return;
}
arr[index] = object;
}
}
public static class DoubleArrayStaticTypesHelper {
public static Double getAt(double[] arr, int index) {
return null == arr ? null : arr[index];
}
public static void putAt(double[] arr, int index, double object) {
if (null == arr) {
return;
}
arr[index] = object;
}
}
/**
* @return true if the class node is either a GString or the LUB of String and GString.
*/
public static boolean isGStringOrGStringStringLUB(ClassNode node) {
return GSTRING_TYPE.equals(node) || GSTRING_STRING_CLASSNODE.equals(node);
}
/**
* @param node the node to be tested
* @return true if the node is using generics types and one of those types is a gstring or string/gstring lub
*/
public static boolean isParameterizedWithGStringOrGStringString(ClassNode node) {
if (node.isArray()) return isParameterizedWithGStringOrGStringString(node.getComponentType());
if (node.isUsingGenerics()) {
GenericsType[] genericsTypes = node.getGenericsTypes();
if (genericsTypes != null) {
for (GenericsType genericsType : genericsTypes) {
if (isGStringOrGStringStringLUB(genericsType.getType())) return true;
}
}
}
return node.getSuperClass() != null && isParameterizedWithGStringOrGStringString(node.getUnresolvedSuperClass());
}
/**
* @param node the node to be tested
* @return true if the node is using generics types and one of those types is a string
*/
public static boolean isParameterizedWithString(ClassNode node) {
if (node.isArray()) return isParameterizedWithString(node.getComponentType());
if (node.isUsingGenerics()) {
GenericsType[] genericsTypes = node.getGenericsTypes();
if (genericsTypes != null) {
for (GenericsType genericsType : genericsTypes) {
if (STRING_TYPE.equals(genericsType.getType())) return true;
}
}
}
return node.getSuperClass() != null && isParameterizedWithString(node.getUnresolvedSuperClass());
}
public static boolean missesGenericsTypes(ClassNode cn) {
if (cn.isArray()) return missesGenericsTypes(cn.getComponentType());
GenericsType[] cnTypes = cn.getGenericsTypes();
GenericsType[] rnTypes = cn.redirect().getGenericsTypes();
if (rnTypes != null && cnTypes == null) return true;
if (cnTypes != null) {
for (GenericsType genericsType : cnTypes) {
if (genericsType.isPlaceholder()) return true;
}
}
return false;
}
/**
* A helper method that can be used to evaluate expressions as found in annotation
* parameters. For example, it will evaluate a constant, be it referenced directly as
* an integer or as a reference to a field.
* <p>
* If this method throws an exception, then the expression cannot be evaluated on its own.
*
* @param expr the expression to be evaluated
* @param config the compiler configuration
* @return the result of the expression
*/
public static Object evaluateExpression(Expression expr, CompilerConfiguration config) {
String className = "Expression$" + UUID.randomUUID().toString().replace('-', '$');
ClassNode node = new ClassNode(className, Opcodes.ACC_PUBLIC, OBJECT_TYPE);
ReturnStatement code = new ReturnStatement(expr);
addGeneratedMethod(node, "eval", Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, code);
CompilerConfiguration copyConf = new CompilerConfiguration(config);
CompilationUnit cu = new CompilationUnit(copyConf);
cu.addClassNode(node);
cu.compile(Phases.CLASS_GENERATION);
List<GroovyClass> classes = cu.getClasses();
Class<?> aClass = cu.getClassLoader().defineClass(className, classes.get(0).getBytes());
try {
return aClass.getMethod("eval").invoke(null);
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw new GroovyBugError(e);
}
}
/**
* Collects all interfaces of a class node, including those defined by the
* super class.
*
* @param node a class for which we want to retrieve all interfaces
* @return a set of interfaces implemented by this class node
*/
public static Set<ClassNode> collectAllInterfaces(ClassNode node) {
Set<ClassNode> result = new HashSet<>();
collectAllInterfaces(node, result);
return result;
}
/**
* Collects all interfaces of a class node, including those defined by the
* super class.
*
* @param node a class for which we want to retrieve all interfaces
* @param out the set where to collect interfaces
*/
private static void collectAllInterfaces(final ClassNode node, final Set<ClassNode> out) {
if (node == null) return;
Set<ClassNode> allInterfaces = node.getAllInterfaces();
out.addAll(allInterfaces);
collectAllInterfaces(node.getSuperClass(), out);
}
/**
* Returns true if the class node represents a the class node for the Class class
* and if the parametrized type is a neither a placeholder or a wildcard. For example,
* the class node Class&lt;Foo&gt; where Foo is a class would return true, but the class
* node for Class&lt;?&gt; would return false.
*
* @param classNode a class node to be tested
* @return true if it is the class node for Class and its generic type is a real class
*/
public static boolean isClassClassNodeWrappingConcreteType(ClassNode classNode) {
GenericsType[] genericsTypes = classNode.getGenericsTypes();
return CLASS_Type.equals(classNode)
&& classNode.isUsingGenerics()
&& genericsTypes != null
&& !genericsTypes[0].isPlaceholder()
&& !genericsTypes[0].isWildcard();
}
public static List<MethodNode> findSetters(ClassNode cn, String setterName, boolean voidOnly) {
List<MethodNode> result = null;
for (MethodNode method : cn.getDeclaredMethods(setterName)) {
if (setterName.equals(method.getName())
&& (!voidOnly || VOID_TYPE == method.getReturnType())
&& method.getParameters().length == 1) {
if (result == null) {
result = new LinkedList<>();
}
result.add(method);
}
}
if (result == null) {
ClassNode parent = cn.getSuperClass();
if (parent != null) {
return findSetters(parent, setterName, voidOnly);
}
return Collections.emptyList();
}
return result;
}
public static ClassNode isTraitSelf(VariableExpression vexp) {
if (Traits.THIS_OBJECT.equals(vexp.getName())) {
Variable accessedVariable = vexp.getAccessedVariable();
ClassNode type = accessedVariable != null ? accessedVariable.getType() : null;
if (accessedVariable instanceof Parameter
&& Traits.isTrait(type)) {
return type;
}
}
return null;
}
}