| /* |
| * 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.ClassNode; |
| import org.codehaus.groovy.ast.ConstructorNode; |
| 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.CastExpression; |
| 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.GeneralUtils; |
| 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.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.Iterator; |
| import java.util.LinkedHashMap; |
| 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.StringJoiner; |
| import java.util.TreeSet; |
| import java.util.UUID; |
| import java.util.regex.Matcher; |
| import java.util.stream.BaseStream; |
| |
| import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod; |
| import static org.apache.groovy.ast.tools.ClassNodeUtils.samePackageName; |
| import static org.apache.groovy.ast.tools.ExpressionUtils.isNullConstant; |
| import static org.codehaus.groovy.ast.ClassHelper.BigInteger_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.COLLECTION_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.Character_TYPE; |
| import static org.codehaus.groovy.ast.ClassHelper.DEPRECATED_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.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.Number_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.findSAM; |
| 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.isBigDecimalType; |
| import static org.codehaus.groovy.ast.ClassHelper.isBigIntegerType; |
| import static org.codehaus.groovy.ast.ClassHelper.isClassType; |
| import static org.codehaus.groovy.ast.ClassHelper.isGStringType; |
| import static org.codehaus.groovy.ast.ClassHelper.isGroovyObjectType; |
| import static org.codehaus.groovy.ast.ClassHelper.isNumberType; |
| import static org.codehaus.groovy.ast.ClassHelper.isObjectType; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean; |
| import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; |
| import static org.codehaus.groovy.ast.ClassHelper.isSAMType; |
| import static org.codehaus.groovy.ast.ClassHelper.isStringType; |
| import static org.codehaus.groovy.ast.ClassHelper.isWrapperBoolean; |
| 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.WideningCategories.implementsInterfaceOrSubclassOf; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.isFloatingCategory; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.isLongCategory; |
| import static org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound; |
| import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean; |
| import static org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport.closeQuietly; |
| 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; |
| |
| /** |
| * 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 BaseStream_TYPE = makeWithoutCaching(BaseStream.class); |
| protected static final ClassNode Collection_TYPE = COLLECTION_TYPE; // TODO: deprecate? |
| protected static final ClassNode Deprecated_TYPE = DEPRECATED_TYPE; // TODO: deprecate? |
| protected static final ClassNode LinkedHashMap_TYPE = makeWithoutCaching(LinkedHashMap.class); |
| protected static final ClassNode LinkedHashSet_TYPE = makeWithoutCaching(LinkedHashSet.class); |
| |
| 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 = 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 = (mn1, mn2) -> { |
| if (mn1.getName().equals(mn2.getName())) { |
| Parameter[] pa1 = mn1.getParameters(); |
| Parameter[] pa2 = mn2.getParameters(); |
| if (pa1.length == pa2.length) { |
| boolean allEqual = true; |
| for (int i = 0, n = pa1.length; i < n && allEqual; i += 1) { |
| allEqual = pa1[i].getType().equals(pa2[i].getType()); |
| } |
| if (allEqual) { |
| if (mn1 instanceof ExtensionMethodNode && mn2 instanceof ExtensionMethodNode) { |
| return StaticTypeCheckingSupport.DGM_METHOD_NODE_COMPARATOR.compare(((ExtensionMethodNode) mn1).getExtensionMethodNode(), ((ExtensionMethodNode) mn2).getExtensionMethodNode()); |
| } |
| return 0; |
| } |
| } else { |
| return pa1.length - pa2.length; |
| } |
| } |
| return 1; |
| }; |
| |
| protected static final ExtensionMethodCache EXTENSION_METHOD_CACHE = ExtensionMethodCache.INSTANCE; |
| |
| public static void clearExtensionMethodCache() { |
| EXTENSION_METHOD_CACHE.cache.clearAll(); |
| } |
| |
| /** |
| * Returns true for expressions of the form x[...] |
| * |
| * @param expression an expression |
| * @return true for array access expressions |
| */ |
| protected static boolean isArrayAccessExpression(final 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 arguments method call arguments |
| * @return true if the name is "with" and arguments consist of a single closure |
| */ |
| public static boolean isWithCall(final String name, final Expression arguments) { |
| if ("with".equals(name) && arguments instanceof ArgumentListExpression) { |
| List<Expression> args = ((ArgumentListExpression) arguments).getExpressions(); |
| if (args.size() == 1 && args.get(0) instanceof ClosureExpression) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Given a variable expression, returns the ultimately accessed variable. |
| * |
| * @param ve a variable expression |
| * @return the target variable |
| */ |
| protected static Variable findTargetVariable(final VariableExpression ve) { |
| Variable accessedVariable = ve.getAccessedVariable(); |
| if (accessedVariable != null && accessedVariable != ve) { |
| if (accessedVariable instanceof VariableExpression) { |
| return findTargetVariable((VariableExpression) accessedVariable); |
| } |
| return accessedVariable; |
| } |
| return ve; |
| } |
| |
| /** |
| * @deprecated Use {@link #findDGMMethodsForClassNode(ClassLoader, ClassNode, String)} instead |
| */ |
| @Deprecated |
| protected static Set<MethodNode> findDGMMethodsForClassNode(final ClassNode clazz, final String name) { |
| return findDGMMethodsForClassNode(MetaClassRegistryImpl.class.getClassLoader(), clazz, name); |
| } |
| |
| public static Set<MethodNode> findDGMMethodsForClassNode(final ClassLoader loader, final ClassNode clazz, final 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(final ClassNode clazz, final String name, final TreeSet<MethodNode> accumulator) { |
| findDGMMethodsForClassNode(MetaClassRegistryImpl.class.getClassLoader(), clazz, name, accumulator); |
| } |
| |
| protected static void findDGMMethodsForClassNode(final ClassLoader loader, final ClassNode clazz, final String name, final 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 (!isObjectType(componentClass) && !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 (!isObjectType(clazz)) { |
| findDGMMethodsForClassNode(loader, OBJECT_TYPE, name, accumulator); |
| } |
| } |
| |
| /** |
| * Checks that arguments and parameter types match. |
| * |
| * @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 |
| */ |
| public static int allParametersAndArgumentsMatch(Parameter[] parameters, final ClassNode[] argumentTypes) { |
| if (parameters == null) { |
| parameters = Parameter.EMPTY_ARRAY; |
| } |
| int dist = 0; |
| if (argumentTypes.length < parameters.length) { |
| return -1; |
| } |
| // we already know there are at least params.length elements in both arrays |
| for (int i = 0, n = parameters.length; i < n; i += 1) { |
| ClassNode paramType = parameters[i].getType(); |
| ClassNode argType = argumentTypes[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. |
| * |
| * @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(final Parameter[] parameters, final ClassNode[] argumentTypes) { |
| int dist = 0; |
| ClassNode ptype = null; |
| for (int i = 0, j = 0, n = parameters.length; i < n; i += 1) { |
| Parameter param = parameters[i]; |
| ClassNode paramType = param.getType(); |
| ClassNode arg = (j >= argumentTypes.length ? null : argumentTypes[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 += 1; |
| 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. |
| * |
| * @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(final Parameter[] parameters, final ClassNode[] argumentTypes) { |
| // 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 = parameters[parameters.length - 1].getType().getComponentType(); |
| for (int i = parameters.length; i < argumentTypes.length; i += 1) { |
| if (!isAssignableTo(argumentTypes[i], vargsBase)) return -1; |
| else dist += getClassDistance(vargsBase, argumentTypes[i]); |
| } |
| return dist; |
| } |
| |
| /** |
| * Checks if the last argument matches the vararg type. |
| * |
| * @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(final Parameter[] parameters, final ClassNode... argumentTypes) { |
| if (!isVargs(parameters)) return -1; |
| int lastParamIndex = parameters.length - 1; |
| if (lastParamIndex == argumentTypes.length) return 0; |
| // two cases remain: |
| // the argument is wrapped in the vargs array or |
| // the argument is an array that can be used for the vargs part directly |
| // testing only the wrapping case since the non-wrapping is done already |
| ClassNode arrayType = parameters[lastParamIndex].getType(); |
| ClassNode elementType = arrayType.getComponentType(); |
| ClassNode argumentType = argumentTypes[argumentTypes.length - 1]; |
| if (isNumberType(elementType) && isNumberType(argumentType) && !getWrapper(elementType).equals(getWrapper(argumentType))) return -1; |
| return isAssignableTo(argumentType, elementType) ? Math.min(getDistance(argumentType, arrayType), getDistance(argumentType, elementType)) : -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. |
| * |
| * @return true if the class node is assignable to the other class node, false otherwise |
| */ |
| public static boolean isAssignableTo(ClassNode type, ClassNode toBeAssignedTo) { |
| if (type == toBeAssignedTo || type == UNKNOWN_PARAMETER_TYPE) return true; |
| if (isPrimitiveType(type)) type = getWrapper(type); |
| if (isPrimitiveType(toBeAssignedTo)) toBeAssignedTo = getWrapper(toBeAssignedTo); |
| 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()) { // GROOVY-10720: check primitive to/from non-primitive |
| ClassNode sourceComponent = type.getComponentType(), targetComponent = toBeAssignedTo.getComponentType(); |
| return (isPrimitiveType(sourceComponent) == isPrimitiveType(targetComponent)) && isAssignableTo(sourceComponent, targetComponent); |
| } |
| if (type.isDerivedFrom(GSTRING_TYPE) && isStringType(toBeAssignedTo)) { |
| return true; |
| } |
| if (isStringType(type) && toBeAssignedTo.isDerivedFrom(GSTRING_TYPE)) { |
| return true; |
| } |
| if (implementsInterfaceOrIsSubclassOf(type, toBeAssignedTo)) { |
| if (toBeAssignedTo.getGenericsTypes() != null) { // perform additional check on generics |
| GenericsType gt = toBeAssignedTo.isGenericsPlaceHolder() ? toBeAssignedTo.getGenericsTypes()[0] : GenericsUtils.buildWildcardType(toBeAssignedTo); |
| return gt.isCompatibleWith(type); |
| } |
| return true; |
| } |
| // GROOVY-10067: unresolved argument like "N extends Number" for parameter like "Integer" |
| if (type.isGenericsPlaceHolder() && type.getUnresolvedName().charAt(0) == '#') { |
| return type.getGenericsTypes()[0].isCompatibleWith(toBeAssignedTo); |
| } |
| return (type.isDerivedFrom(CLOSURE_TYPE) && isSAMType(toBeAssignedTo)); |
| } |
| |
| @Deprecated |
| static boolean isVargs(final Parameter[] parameters) { |
| return ParameterUtils.isVargs(parameters); |
| } |
| |
| public static boolean isCompareToBoolean(final int op) { |
| return op == COMPARE_LESS_THAN || op == COMPARE_LESS_THAN_EQUAL |
| || op == COMPARE_GREATER_THAN || op == COMPARE_GREATER_THAN_EQUAL; |
| } |
| |
| static boolean isArrayOp(final int op) { |
| return op == LEFT_SQUARE_BRACKET; |
| } |
| |
| static boolean isBoolIntrinsicOp(final int op) { |
| switch (op) { |
| case LOGICAL_AND: |
| case LOGICAL_OR: |
| case COMPARE_NOT_IDENTICAL: |
| case COMPARE_IDENTICAL: |
| case MATCH_REGEX: |
| case KEYWORD_INSTANCEOF: |
| case COMPARE_NOT_INSTANCEOF: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static boolean isPowerOperator(final int op) { |
| return op == POWER || op == POWER_EQUAL; |
| } |
| |
| static String getOperationName(final int op) { |
| switch (op) { |
| case COMPARE_EQUAL: |
| case COMPARE_NOT_EQUAL: |
| // this is only correct in this specific context; 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_LESS_THAN: |
| case COMPARE_LESS_THAN_EQUAL: |
| case COMPARE_GREATER_THAN: |
| case COMPARE_GREATER_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(final 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(final 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(final 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(final 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(final ClassNode left, final ClassNode right) { |
| return checkCompatibleAssignmentTypes(left, right, null); |
| } |
| |
| public static boolean checkCompatibleAssignmentTypes(final ClassNode left, final ClassNode right, final Expression rightExpression) { |
| return checkCompatibleAssignmentTypes(left, right, rightExpression, true); |
| } |
| |
| /** |
| * Everything that can be done by {@code castToType} should be allowed for assignment. |
| * |
| * @see org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation#castToType(Object,Class) |
| */ |
| public static boolean checkCompatibleAssignmentTypes(final ClassNode left, final ClassNode right, final Expression rightExpression, final boolean allowConstructorCoercion) { |
| if (!isPrimitiveType(left) && isNullConstant(rightExpression)) { |
| return true; |
| } |
| |
| if (left.isArray()) { |
| if (right.isArray()) { |
| ClassNode leftComponent = left.getComponentType(); |
| ClassNode rightComponent = right.getComponentType(); |
| if (isPrimitiveType(leftComponent) != isPrimitiveType(rightComponent)) return false; |
| return checkCompatibleAssignmentTypes(leftComponent, rightComponent, rightExpression, false); |
| } |
| if (GeneralUtils.isOrImplements(right, Collection_TYPE) && !(rightExpression instanceof ListExpression)) { |
| GenericsType elementType = GenericsUtils.parameterizeType(right, Collection_TYPE).getGenericsTypes()[0]; |
| return OBJECT_TYPE.equals(left.getComponentType()) // Object[] can accept any collection element type(s) |
| || (elementType.getLowerBound() == null && isCovariant(extractType(elementType), left.getComponentType())); |
| // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GROOVY-8984: "? super T" is only compatible with an Object[] target |
| } |
| if (GeneralUtils.isOrImplements(right, BaseStream_TYPE)) { |
| GenericsType elementType = GenericsUtils.parameterizeType(right, BaseStream_TYPE).getGenericsTypes()[0]; |
| return isObjectType(left.getComponentType()) // Object[] can accept any stream API element type(s) |
| || (elementType.getLowerBound() == null && isCovariant(extractType(elementType), getWrapper(left.getComponentType()))); |
| } |
| } |
| |
| ClassNode leftRedirect = left.redirect(); |
| ClassNode rightRedirect = right.redirect(); |
| if (leftRedirect == rightRedirect) return true; |
| |
| if (leftRedirect == VOID_TYPE) return rightRedirect == void_WRAPPER_TYPE; |
| if (leftRedirect == void_WRAPPER_TYPE) return rightRedirect == VOID_TYPE; |
| |
| if (isLongCategory(getUnwrapper(leftRedirect))) { |
| // byte, char, int, long or short can be assigned any base number |
| if (isNumberType(rightRedirect) /*|| rightRedirect == char_TYPE*/) { |
| return true; |
| } |
| if (leftRedirect == char_TYPE && rightRedirect == Character_TYPE) return true; |
| if (leftRedirect == Character_TYPE && rightRedirect == char_TYPE) return true; |
| if ((leftRedirect == char_TYPE || leftRedirect == Character_TYPE) && rightRedirect == STRING_TYPE) { |
| return rightExpression instanceof ConstantExpression && rightExpression.getText().length() == 1; |
| } |
| } else if (isFloatingCategory(getUnwrapper(leftRedirect))) { |
| // float or double can be assigned any base number type or BigDecimal |
| if (isNumberType(rightRedirect) || isBigDecimalType(rightRedirect)) { |
| return true; |
| } |
| } else if (left.isGenericsPlaceHolder()) { // must precede non-final types |
| return right.getUnresolvedName().charAt(0) != '#' // RHS not adaptable |
| ? left.getGenericsTypes()[0].isCompatibleWith(right) // GROOVY-7307, GROOVY-9952, et al. |
| : implementsInterfaceOrSubclassOf(leftRedirect, rightRedirect); // GROOVY-10067, GROOVY-10342 |
| |
| } else if (isBigDecimalType(leftRedirect) || Number_TYPE.equals(leftRedirect)) { |
| // BigDecimal or Number can be assigned any derivitave of java.lang.Number |
| if (isNumberType(rightRedirect) || rightRedirect.isDerivedFrom(Number_TYPE)) { |
| return true; |
| } |
| } else if (isBigIntegerType(leftRedirect)) { |
| // BigInteger can be assigned byte, char, int, long, short or BigInteger |
| if (isLongCategory(getUnwrapper(rightRedirect)) || rightRedirect.isDerivedFrom(BigInteger_TYPE)) { |
| return true; |
| } |
| } else if (leftRedirect.isDerivedFrom(Enum_Type)) { |
| // Enum types can be assigned String or GString (triggers `valueOf` call) |
| if (rightRedirect == STRING_TYPE || isGStringOrGStringStringLUB(rightRedirect)) { |
| return true; |
| } |
| } else if (isWildcardLeftHandSide(leftRedirect)) { |
| // Object, String, [Bb]oolean or Class can be assigned anything (except null to boolean) |
| return !(leftRedirect == boolean_TYPE && isNullConstant(rightExpression)); |
| } |
| |
| // 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 (rightRedirect.isArray() && !leftRedirect.isArray()) { |
| return false; |
| } |
| return true; |
| } |
| |
| if (implementsInterfaceOrSubclassOf(right, left)) { |
| return true; |
| } |
| |
| if (right.isDerivedFrom(CLOSURE_TYPE) && isSAMType(left)) { |
| return true; |
| } |
| |
| // GROOVY-7316, GROOVY-10256: "Type x = m()" given "def <T> T m()"; T adapts to target |
| return right.isGenericsPlaceHolder() && right.asGenericsType().isCompatibleWith(left); |
| } |
| |
| 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) { |
| return (isObjectType(node) |
| || isStringType(node) |
| || isPrimitiveBoolean(node) |
| || isWrapperBoolean(node) |
| || isClassType(node)); |
| } |
| |
| public static boolean isBeingCompiled(final ClassNode node) { |
| return (node.getCompileUnit() != null); |
| } |
| |
| @Deprecated |
| static boolean checkPossibleLooseOfPrecision(final ClassNode left, final ClassNode right, final Expression rightExpr) { |
| return checkPossibleLossOfPrecision(left, right, rightExpr); |
| } |
| |
| static boolean checkPossibleLossOfPrecision(final ClassNode left, final ClassNode right, final 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 loss here |
| } |
| } |
| return true; // possible loss of precision |
| } |
| |
| static String toMethodParametersString(final String methodName, final ClassNode... parameters) { |
| if (parameters == null || parameters.length == 0) return methodName + "()"; |
| |
| StringJoiner joiner = new StringJoiner(", ", methodName + "(", ")"); |
| for (ClassNode parameter : parameters) { |
| joiner.add(prettyPrintType(parameter)); |
| } |
| return joiner.toString(); |
| } |
| |
| /** |
| * Returns string representation of type with generics. Arrays are indicated |
| * with trailing "[]". |
| */ |
| static String prettyPrintType(final ClassNode type) { |
| if (type.getUnresolvedName().charAt(0) == '#') { |
| return type.redirect().toString(false); |
| } |
| return type.toString(false); |
| } |
| |
| /** |
| * Returns string representation of type *no* generics. Arrays are indicated |
| * with trailing "[]". |
| */ |
| static String prettyPrintTypeName(final ClassNode type) { |
| if (type.isArray()) { |
| return prettyPrintTypeName(type.getComponentType()) + "[]"; |
| } |
| return type.isGenericsPlaceHolder() ? type.getUnresolvedName() : type.getText(); |
| } |
| |
| public static boolean implementsInterfaceOrIsSubclassOf(final ClassNode type, final 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) { |
| for (ClassNode delegate : ((UnionTypeClassNode) superOrInterface).getDelegates()) { |
| if (implementsInterfaceOrIsSubclassOf(type, delegate)) return true; |
| } |
| } |
| if (type.isArray() && superOrInterface.isArray()) { |
| return implementsInterfaceOrIsSubclassOf(type.getComponentType(), superOrInterface.getComponentType()); |
| } |
| if (isGroovyObjectType(superOrInterface) && isBeingCompiled(type) && !type.isInterface()) {//TODO: !POJO !Trait |
| 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) |
| || receiver == UNKNOWN_PARAMETER_TYPE) { |
| return dist; |
| } |
| if (receiver.isArray()) { |
| dist += 256; // GROOVY-5114: Object[] vs Object |
| } |
| if (compare.isInterface()) { MethodNode sam; |
| if (receiver.implementsInterface(compare)) { |
| return dist + getMaximumInterfaceDistance(receiver, compare); |
| } else if (receiver.equals(CLOSURE_TYPE) && (sam = findSAM(compare)) != null) { |
| // GROOVY-9881: in case of multiple overloads, give preference to equal parameter count |
| Integer closureParamCount = receiver.getNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS); |
| if (closureParamCount != null && closureParamCount == sam.getParameters().length) dist -= 1; |
| |
| return dist + 13; // GROOVY-9852: @FunctionalInterface vs Object |
| } |
| } |
| ClassNode cn = isPrimitiveType(receiver) && !isPrimitiveType(compare) ? getWrapper(receiver) : receiver; |
| while (cn != null && !cn.equals(compare)) { |
| cn = cn.getSuperClass(); |
| dist += 1; |
| if (isObjectType(cn)) |
| dist += 1; |
| dist = (dist + 1) << 1; |
| } |
| return dist; |
| } |
| |
| private static int getMaximumInterfaceDistance(final ClassNode c, final 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) { |
| methods.addAll(findDGMMethodsForClassNode(loader, receiver, name)); |
| return methods.isEmpty() ? methods : chooseBestMethod(receiver, methods, args); |
| } |
| |
| /** |
| * 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(final ClassNode node) { |
| return GenericsUtils.hasUnresolvedGenerics(node); |
| } |
| |
| /** |
| * Returns the method(s) which best fit the argument types. |
| * |
| * @return zero or more results |
| */ |
| public static List<MethodNode> chooseBestMethod(final ClassNode receiver, final Collection<MethodNode> methods, final ClassNode... argumentTypes) { |
| if (!asBoolean(methods)) { |
| return Collections.emptyList(); |
| } |
| |
| int bestDist = Integer.MAX_VALUE; |
| List<MethodNode> bestChoices = new LinkedList<>(); |
| boolean duckType = receiver instanceof UnionTypeClassNode; // GROOVY-8965: type disjunction |
| boolean noCulling = methods.size() <= 1 || "<init>".equals(methods.iterator().next().getName()); |
| Iterable<MethodNode> candidates = noCulling ? methods : removeCovariantsAndInterfaceEquivalents(methods, duckType); |
| |
| for (MethodNode candidate : candidates) { |
| MethodNode safeNode = candidate; |
| ClassNode[] safeArgs = argumentTypes; |
| boolean isExtensionMethod = candidate instanceof ExtensionMethodNode; |
| if (isExtensionMethod) { |
| int nArgs = argumentTypes.length; |
| safeArgs = new ClassNode[nArgs + 1]; |
| System.arraycopy(argumentTypes, 0, safeArgs, 1, nArgs); |
| safeArgs[0] = receiver; // prepend self-type as first argument |
| safeNode = ((ExtensionMethodNode) candidate).getExtensionMethodNode(); |
| } |
| |
| /* TODO: corner case |
| class B extends A {} |
| Animal foo(A a) {} |
| Person foo(B b) {} |
| |
| B b = new B() |
| Person p = foo(b) |
| */ |
| |
| ClassNode declaringClass = candidate.getDeclaringClass(); |
| ClassNode actualReceiver = receiver != null ? receiver : declaringClass; |
| |
| Map<GenericsType, GenericsType> spec; |
| if (candidate.isStatic()) { |
| spec = Collections.emptyMap(); // none visible |
| } else { |
| spec = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(declaringClass, actualReceiver); |
| GenericsType[] methodGenerics = candidate.getGenericsTypes(); |
| if (methodGenerics != null) { // GROOVY-10322: remove hidden type parameters |
| for (int i = 0, n = methodGenerics.length; i < n && !spec.isEmpty(); i += 1) { |
| for (Iterator<GenericsType> it = spec.keySet().iterator(); it.hasNext(); ) { |
| if (it.next().getName().equals(methodGenerics[i].getName())) it.remove(); |
| } |
| } |
| } |
| } |
| |
| Parameter[] params = makeRawTypes(safeNode.getParameters(), spec); |
| int dist = measureParametersAndArgumentsDistance(params, safeArgs); |
| if (dist >= 0) { |
| dist += getClassDistance(declaringClass, actualReceiver); |
| dist += getExtensionDistance(isExtensionMethod); |
| if (dist < bestDist) { |
| bestDist = dist; |
| bestChoices.clear(); |
| bestChoices.add(candidate); |
| } else if (dist == bestDist) { |
| bestChoices.add(candidate); |
| } |
| } |
| } |
| if (bestChoices.size() > 1 && !duckType) { |
| // GROOVY-6849: prefer extension method 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(final Parameter[] parameters, final ClassNode[] argumentTypes) { |
| int dist = -1; |
| if (parameters.length == argumentTypes.length) { |
| int allMatch = allParametersAndArgumentsMatch(parameters, argumentTypes); |
| int endMatch = -1; |
| if (isVargs(parameters) && firstParametersAndArgumentsMatch(parameters, argumentTypes) >= 0) { |
| endMatch = lastArgMatchesVarg(parameters, argumentTypes); |
| if (endMatch >= 0) { |
| endMatch += getVarargsDistance(parameters); |
| } |
| } |
| dist = (allMatch >= 0 ? Math.max(allMatch, endMatch) : endMatch); |
| } else if (isVargs(parameters)) { |
| dist = firstParametersAndArgumentsMatch(parameters, argumentTypes); |
| 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(parameters); |
| // 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 (parameters.length < argumentTypes.length) { |
| // (3) there is more than one argument for the vargs array |
| int excessArgumentsDistance = excessArgumentsMatchesVargsParameter(parameters, argumentTypes); |
| if (excessArgumentsDistance >= 0) { |
| dist += excessArgumentsDistance; |
| } else { |
| dist = -1; |
| } |
| } |
| } |
| } |
| return dist; |
| } |
| |
| private static int firstParametersAndArgumentsMatch(final Parameter[] parameters, final ClassNode[] safeArgumentTypes) { |
| int dist = 0; |
| // check first parameters |
| if (parameters.length > 0) { |
| Parameter[] firstParams = new Parameter[parameters.length - 1]; |
| System.arraycopy(parameters, 0, firstParams, 0, firstParams.length); |
| dist = allParametersAndArgumentsMatch(firstParams, safeArgumentTypes); |
| } |
| return dist; |
| } |
| |
| private static int getVarargsDistance(final Parameter[] parameters) { |
| return 256 - parameters.length; // ensure exact matches are preferred over vargs |
| } |
| |
| private static int getClassDistance(final ClassNode declaringClassForDistance, final ClassNode actualReceiverForDistance) { |
| if (actualReceiverForDistance.equals(declaringClassForDistance)) { |
| return 0; |
| } |
| return getDistance(actualReceiverForDistance, declaringClassForDistance); |
| } |
| |
| private static int getExtensionDistance(final boolean isExtensionMethodNode) { |
| return isExtensionMethodNode ? 0 : 1; |
| } |
| |
| private static Parameter[] makeRawTypes(final Parameter[] parameters, final Map<GenericsType, GenericsType> genericsPlaceholderAndTypeMap) { |
| return Arrays.stream(parameters).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(gt -> !gt.isPlaceholder() ? gt.getType() : makeRawType(gt.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 List<MethodNode> removeCovariantsAndInterfaceEquivalents(final Collection<MethodNode> collection, final boolean disjoint) { |
| List<MethodNode> list = new ArrayList<>(new LinkedHashSet<>(collection)), toBeRemoved = new ArrayList<>(); |
| for (int i = 0, n = list.size(); i < n - 1; i += 1) { |
| MethodNode one = list.get(i); |
| if (toBeRemoved.contains(one)) continue; |
| for (int j = i + 1; j < n; j += 1) { |
| MethodNode two = list.get(j); |
| if (toBeRemoved.contains(two)) continue; |
| if (one.getParameters().length == two.getParameters().length) { |
| ClassNode oneDC = one.getDeclaringClass(), twoDC = two.getDeclaringClass(); |
| if (oneDC == twoDC) { |
| if (ParameterUtils.parametersEqual(one.getParameters(), two.getParameters())) { |
| ClassNode oneRT = one.getReturnType(), twoRT = two.getReturnType(); |
| if (isCovariant(oneRT, twoRT)) { |
| toBeRemoved.add(two); |
| } else if (isCovariant(twoRT, oneRT)) { |
| toBeRemoved.add(one); |
| } |
| } else { |
| // imperfect solution to determining if two methods are |
| // equivalent, for example String#compareTo(Object) and |
| // String#compareTo(String) -- in that case, the Object |
| // version is marked as synthetic |
| if (one.isSynthetic() && !two.isSynthetic()) { |
| toBeRemoved.add(one); |
| } else if (two.isSynthetic() && !one.isSynthetic()) { |
| toBeRemoved.add(two); |
| } |
| } |
| } else if (!oneDC.equals(twoDC)) { |
| if (ParameterUtils.parametersEqual(one.getParameters(), two.getParameters())) { |
| // GROOVY-6882, GROOVY-6970: drop overridden or interface equivalent method |
| if (twoDC.isInterface() ? oneDC.implementsInterface(twoDC) : oneDC.isDerivedFrom(twoDC)) { |
| toBeRemoved.add(two); |
| } else if (oneDC.isInterface() ? (disjoint ? twoDC.implementsInterface(oneDC) : twoDC.isInterface()) : twoDC.isDerivedFrom(oneDC)) { |
| toBeRemoved.add(one); |
| } |
| } |
| } |
| } |
| } |
| } |
| if (toBeRemoved.isEmpty()) return list; |
| |
| List<MethodNode> result = new LinkedList<>(list); |
| result.removeAll(toBeRemoved); |
| return result; |
| } |
| |
| private static boolean isCovariant(final ClassNode one, final ClassNode two) { |
| if (one.isArray() && two.isArray()) { |
| return isCovariant(one.getComponentType(), two.getComponentType()); |
| } |
| return (one.isDerivedFrom(two) || one.implementsInterface(two)); |
| } |
| |
| /** |
| * 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, n = methodParameters.length; i < n; i += 1) { |
| 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 makes use of generic types. If 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(final ClassNode cn) { |
| if (cn.isArray()) { |
| return isUsingGenericsOrIsArrayUsingGenerics(cn.getComponentType()); |
| } |
| if (cn.isUsingGenerics()) { |
| if (cn.getGenericsTypes() != null) { |
| return true; |
| } |
| ClassNode oc = cn.getOuterClass(); |
| if (oc != null && oc.getGenericsTypes() != null |
| && (cn.getModifiers() & Opcodes.ACC_STATIC) == 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Given a generics type representing SomeClass<T,V> and a resolved placeholder map, returns a new generics type |
| * for which placeholders are resolved recursively. |
| */ |
| protected static GenericsType fullyResolve(GenericsType gt, final 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.isArray()) { |
| return fullyResolveType(type.getComponentType(), placeholders).makeArray(); |
| } |
| if (!type.isUsingGenerics()) { |
| return type; |
| } |
| if (type.isGenericsPlaceHolder()) { |
| GenericsType gt = placeholders.get(new GenericsTypeName(type.getUnresolvedName())); |
| if (gt != null) { |
| return gt.getType(); |
| } |
| ClassNode cn = type.redirect(); |
| return cn != type ? cn : OBJECT_TYPE; |
| } |
| |
| GenericsType[] gts = type.getGenericsTypes(); |
| if (asBoolean(gts)) { |
| gts = gts.clone(); |
| for (int i = 0, n = gts.length; i < n; i += 1) { |
| GenericsType gt = gts[i]; |
| if (gt.isPlaceholder()) { String name = gt.getName(); |
| gt = placeholders.get(new GenericsTypeName(name)); |
| if (gt == null) gt = extractType(gts[i]).asGenericsType(); |
| // GROOVY-10364: skip placeholder from the enclosing context |
| if (gt.isPlaceholder() && gt.getName().equals(name)) continue; |
| |
| gts[i] = gt; |
| } else { |
| gts[i] = fullyResolve(gt, placeholders); |
| } |
| } |
| } |
| |
| ClassNode cn = type.getPlainNodeReference(); |
| cn.setGenericsTypes(gts); |
| return cn; |
| } |
| |
| /** |
| * 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(final ClassNode parameterType, final ClassNode argumentType, final boolean lastArg) { |
| if (UNKNOWN_PARAMETER_TYPE == argumentType) { // argument is null |
| return !isPrimitiveType(parameterType); |
| } |
| if (!isAssignableTo(argumentType, parameterType)) { |
| if (!lastArg || !parameterType.isArray() |
| || !isAssignableTo(argumentType, parameterType.getComponentType())) { |
| return false; // incompatible assignment |
| } |
| } |
| 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; |
| } |
| |
| protected static boolean typeCheckMethodsWithGenerics(final ClassNode receiver, final ClassNode[] argumentTypes, final MethodNode candidateMethod) { |
| if (candidateMethod instanceof ExtensionMethodNode) { |
| ClassNode[] realTypes = new ClassNode[argumentTypes.length + 1]; |
| realTypes[0] = receiver; // object expression is implicit argument |
| System.arraycopy(argumentTypes, 0, realTypes, 1, argumentTypes.length); |
| MethodNode realMethod = ((ExtensionMethodNode) candidateMethod).getExtensionMethodNode(); |
| return typeCheckMethodsWithGenerics(realMethod.getDeclaringClass(), realTypes, realMethod, true); |
| } |
| |
| if (receiver.isUsingGenerics() |
| && isClassType(receiver) |
| && !isClassType(candidateMethod.getDeclaringClass())) { |
| return typeCheckMethodsWithGenerics(receiver.getGenericsTypes()[0].getType(), argumentTypes, candidateMethod); |
| } |
| |
| return typeCheckMethodsWithGenerics(receiver, argumentTypes, candidateMethod, false); |
| } |
| |
| private static boolean typeCheckMethodsWithGenerics(final ClassNode receiver, final ClassNode[] argumentTypes, final MethodNode candidateMethod, final boolean isExtensionMethod) { |
| Parameter[] parameters = candidateMethod.getParameters(); |
| if (parameters.length == 0 || parameters.length > argumentTypes.length) { |
| // this is a limitation that must be removed in a future version; we |
| // cannot check generic type arguments if there is default argument! |
| return true; |
| } |
| |
| boolean failure = false; |
| Set<GenericsTypeName> fixedPlaceHolders = Collections.emptySet(); |
| Map<GenericsTypeName, GenericsType> candidateGenerics = new HashMap<>(); |
| // 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 |
| boolean skipBecauseOfInnerClassNotReceiver = !implementsInterfaceOrIsSubclassOf(receiver, candidateMethod.getDeclaringClass()); |
| if (!skipBecauseOfInnerClassNotReceiver) { |
| if (candidateMethod instanceof ConstructorNode) { |
| candidateGenerics = GenericsUtils.extractPlaceholders(receiver); |
| fixedPlaceHolders = new HashSet<>(candidateGenerics.keySet()); |
| } else { |
| failure = inferenceCheck(fixedPlaceHolders, candidateGenerics, candidateMethod.getDeclaringClass(), receiver, false); |
| |
| GenericsType[] gts = candidateMethod.getGenericsTypes(); |
| if (candidateMethod.isStatic()) { |
| candidateGenerics.clear(); // not in scope |
| } else if (gts != null) { |
| // first remove hidden params |
| for (GenericsType gt : gts) { |
| candidateGenerics.remove(new GenericsTypeName(gt.getName())); |
| } |
| // GROOVY-8034: non-static method may use class generics |
| gts = applyGenericsContext(candidateGenerics, gts); |
| } |
| GenericsUtils.extractPlaceholders(GenericsUtils.makeClassSafe0(OBJECT_TYPE, gts), candidateGenerics); |
| |
| // 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. |
| fixedPlaceHolders = extractResolvedPlaceHolders(candidateGenerics); |
| } |
| } |
| |
| int lastParamIndex = parameters.length - 1; |
| for (int i = 0, n = argumentTypes.length; i < n; i += 1) { |
| ClassNode parameterType = parameters[Math.min(i, lastParamIndex)].getOriginType(); |
| ClassNode argumentType = StaticTypeCheckingVisitor.wrapTypeIfNecessary(argumentTypes[i]); |
| failure |= inferenceCheck(fixedPlaceHolders, candidateGenerics, parameterType, argumentType, i >= lastParamIndex); |
| |
| if (i == 0 && isExtensionMethod) { // re-load fixed names for extension |
| fixedPlaceHolders = extractResolvedPlaceHolders(candidateGenerics); |
| } |
| } |
| return !failure; |
| } |
| |
| private static Set<GenericsTypeName> extractResolvedPlaceHolders(final 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(final Set<GenericsTypeName> fixedPlaceHolders, final Map<GenericsTypeName, GenericsType> resolvedMethodGenerics, ClassNode type, final ClassNode wrappedArgument, final boolean lastArg) { |
| // GROOVY-8090: handle generics varargs like "T x = ...; Arrays.asList(x)" |
| if (lastArg && type.isArray() && type.getComponentType().isGenericsPlaceHolder() |
| && !wrappedArgument.isArray() && wrappedArgument.isGenericsPlaceHolder()) { |
| 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 |
| Map<GenericsTypeName, GenericsType> connections = new LinkedHashMap<>(); |
| extractGenericsConnections(connections, wrappedArgument, type); |
| |
| // each new connection must comply with previous connections |
| for (Map.Entry<GenericsTypeName, GenericsType> entry : connections.entrySet()) { |
| GenericsType candidate = entry.getValue(), resolved = resolvedMethodGenerics.get(entry.getKey()); |
| if (resolved == null || (candidate.isPlaceholder() && !hasNonTrivialBounds(candidate))) continue; |
| |
| if (!compatibleConnection(resolved, candidate)) { |
| if (!resolved.isPlaceholder() && !resolved.isWildcard() |
| && !fixedPlaceHolders.contains(entry.getKey())) { |
| // GROOVY-5692, GROOVY-10006: multiple witnesses |
| if (compatibleConnection(candidate, resolved)) { |
| // was "T=Integer" and now is "T=Number" or "T=Object" |
| resolvedMethodGenerics.put(entry.getKey(), candidate); |
| continue; |
| } else if (!candidate.isPlaceholder() && !candidate.isWildcard()) { |
| // combine "T=Integer" and "T=String" to produce "T=? extends Serializable & Comparable<...>" |
| ClassNode lub = lowestUpperBound(candidate.getType(), resolved.getType()); |
| resolvedMethodGenerics.put(entry.getKey(), lub.asGenericsType()); |
| continue; |
| } |
| } |
| return true; // incompatible |
| } |
| } |
| |
| connections.keySet().removeAll(fixedPlaceHolders); // GROOVY-10337 |
| |
| // apply the new information to refine the method level information so |
| // that the information slowly becomes 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 |
| ClassNode resolvedType = applyGenericsContext(resolvedMethodGenerics, type); |
| return !typeCheckMethodArgumentWithGenerics(resolvedType, wrappedArgument, lastArg); |
| } |
| |
| private static boolean compatibleConnection(final GenericsType resolved, final GenericsType 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 resolvedType; |
| if (hasNonTrivialBounds(resolved)) { |
| resolvedType = getCombinedBoundType(resolved); |
| resolvedType = resolvedType.redirect().getPlainNodeReference(); |
| } else if (!resolved.isPlaceholder()) { |
| resolvedType = resolved.getType().getPlainNodeReference(); |
| } else { |
| return true; |
| } |
| |
| GenericsType gt; |
| if (connection.isWildcard()) { |
| gt = connection; |
| } else { // test compatibility with "? super Type" |
| ClassNode lowerBound = connection.getType().getPlainNodeReference(); |
| if (hasNonTrivialBounds(connection)) { |
| lowerBound.setGenericsTypes(new GenericsType[] {connection}); |
| } |
| gt = new GenericsType(makeWithoutCaching("?"), null, lowerBound); |
| gt.setWildcard(true); |
| } |
| return gt.isCompatibleWith(resolvedType); |
| } |
| |
| private static void addMissingEntries(final Map<GenericsTypeName, GenericsType> connections, final 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, final ClassNode currentType) { |
| ClassNode type = currentType; // GROOVY-10280, et al. |
| type = applyGenericsContext(resolvedPlaceholders, type); |
| type = applyGenericsContext(placeholdersFromContext, type); |
| return type; |
| } |
| |
| static void applyGenericsConnections(final Map<GenericsTypeName, GenericsType> connections, final Map<GenericsTypeName, GenericsType> resolvedPlaceholders) { |
| if (connections == null || connections.isEmpty()) return; |
| for (Map.Entry<GenericsTypeName, GenericsType> entry : resolvedPlaceholders.entrySet()) { |
| // entry could be T=T, T=T extends U, T=V, T=String, T=? extends String, etc. |
| GenericsType oldValue = entry.getValue(); |
| if (oldValue.isPlaceholder()) { // T=T or V, not T=String or ? ... |
| GenericsTypeName name = new GenericsTypeName(oldValue.getName()); |
| GenericsType newValue = connections.get(name); // find "V" in T=V |
| if (newValue == oldValue) continue; |
| if (newValue == null) { |
| newValue = connections.get(entry.getKey()); |
| if (newValue != null) { // GROOVY-10315, GROOVY-10317 |
| newValue = getCombinedGenericsType(oldValue, newValue); |
| } |
| } |
| if (newValue == null) { |
| entry.setValue(newValue = applyGenericsContext(connections, oldValue)); |
| } else if (!newValue.isPlaceholder() || newValue != resolvedPlaceholders.get(name)) { |
| // GROOVY-6787: Don't override the original if the replacement doesn't respect the bounds otherwise |
| // the original bounds are lost, which can result in accepting an incompatible type as an argument! |
| ClassNode replacementType = extractType(newValue); |
| ClassNode suitabilityType = !replacementType.isGenericsPlaceHolder() |
| ? replacementType : Optional.ofNullable(replacementType.getGenericsTypes()) |
| .map(gts -> extractType(gts[0])).orElse(replacementType.redirect()); |
| |
| if (oldValue.isCompatibleWith(suitabilityType)) { |
| if (newValue.isWildcard() && newValue.getLowerBound() == null && newValue.getUpperBounds() == null) { |
| // GROOVY-9998: apply upper/lower bound for unknown |
| entry.setValue(replacementType.asGenericsType()); |
| } else { |
| entry.setValue(newValue); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static ClassNode extractType(GenericsType gt) { |
| ClassNode cn; |
| if (!gt.isPlaceholder()) { |
| cn = getCombinedBoundType(gt); |
| } else { |
| // discard the placeholder |
| cn = gt.getType().redirect(); |
| |
| if (gt.getType().getGenericsTypes() != null) |
| gt = gt.getType().getGenericsTypes()[0]; |
| |
| if (gt.getLowerBound() != null) { |
| cn = gt.getLowerBound(); |
| } else if (asBoolean(gt.getUpperBounds())) { |
| cn = gt.getUpperBounds()[0]; |
| } |
| } |
| return cn; |
| } |
| |
| private static boolean equalIncludingGenerics(final GenericsType one, final GenericsType two) { |
| if (one == two) return true; |
| if (one.isWildcard() != two.isWildcard()) return false; |
| if (one.isPlaceholder() != two.isPlaceholder()) return false; |
| if (!equalIncludingGenerics(one.getType(), two.getType())) return false; |
| ClassNode lower1 = one.getLowerBound(); |
| ClassNode lower2 = two.getLowerBound(); |
| if ((lower1 == null) ^ (lower2 == null)) return false; |
| if (lower1 != lower2) { |
| if (!equalIncludingGenerics(lower1, lower2)) return false; |
| } |
| ClassNode[] upper1 = one.getUpperBounds(); |
| ClassNode[] upper2 = two.getUpperBounds(); |
| if ((upper1 == null) ^ (upper2 == null)) return false; |
| if (upper1 != upper2) { |
| if (upper1.length != upper2.length) return false; |
| for (int i = 0, n = upper1.length; i < n; i += 1) { |
| if (!equalIncludingGenerics(upper1[i], upper2[i])) return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean equalIncludingGenerics(final ClassNode one, final ClassNode two) { |
| if (one == two) return true; |
| if (one.isGenericsPlaceHolder() != two.isGenericsPlaceHolder()) return false; |
| if (!one.equals(two)) return false; |
| GenericsType[] gt1 = one.getGenericsTypes(); |
| GenericsType[] gt2 = two.getGenericsTypes(); |
| if ((gt1 == null) ^ (gt2 == null)) return false; |
| if (gt1 != gt2) { |
| if (gt1.length != gt2.length) return false; |
| for (int i = 0, n = gt1.length; i < n; i += 1) { |
| if (!equalIncludingGenerics(gt1[i], gt2[i])) return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Uses supplied type to make a connection from usage to declaration. |
| * <p> |
| * The method operates in two modes: |
| * <ul> |
| * <li>For type !instanceof target a structural compare will be done |
| * (for example Type<T> and List<E> to get E -> T) |
| * <li>If type equals target, a structural match is done as well |
| * (for example Collection<T> and Collection<E> to get E -> T) |
| * <li>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<E> |
| * to get E -> Integer, since IntRange is an AbstractList<Integer>) |
| * </ul> |
| * Should the target not have any generics this method does nothing. |
| */ |
| static void extractGenericsConnections(final Map<GenericsTypeName, GenericsType> connections, final ClassNode type, final ClassNode target) { |
| if (target == null || target == type || (!target.isGenericsPlaceHolder() && !isUsingGenericsOrIsArrayUsingGenerics(target))) return; |
| if (type == null || type == UNKNOWN_PARAMETER_TYPE) return; |
| |
| if (target.isGenericsPlaceHolder()) { |
| storeGenericsConnection(connections, target.getUnresolvedName(), new GenericsType(type)); |
| |
| } else if (type.isGenericsPlaceHolder()) { |
| // "T extends java.util.List<X> -> java.util.List<E>" vs "java.util.List<E>" |
| extractGenericsConnections(connections, extractType(new GenericsType(type)), target); |
| |
| } else if (type.isArray() && target.isArray()) { |
| extractGenericsConnections(connections, type.getComponentType(), target.getComponentType()); |
| |
| } else if (type.equals(CLOSURE_TYPE) && isSAMType(target)) { |
| // GROOVY-9974, GROOVY-10052: Lambda, Closure, Pointer or Reference for SAM-type receiver |
| ClassNode returnType = StaticTypeCheckingVisitor.wrapTypeIfNecessary(GenericsUtils.parameterizeSAM(target).getV2()); |
| extractGenericsConnections(connections, type.getGenericsTypes(), new GenericsType[]{ returnType.asGenericsType() }); |
| |
| } else if (type.equals(target)) { |
| extractGenericsConnections(connections, type.getGenericsTypes(), target.getGenericsTypes()); |
| extractGenericsConnections(connections, type.getNodeMetaData("outer.class"), target.getOuterClass()); //GROOVY-10646 |
| |
| } else if (implementsInterfaceOrIsSubclassOf(type, target)) { |
| ClassNode goal = GenericsUtils.parameterizeType(type, target); |
| extractGenericsConnections(connections, goal.getGenericsTypes(), target.getGenericsTypes()); |
| } |
| } |
| |
| private static void extractGenericsConnections(final Map<GenericsTypeName, GenericsType> connections, final GenericsType[] usage, final 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, n = usage.length; i < n; i += 1) { |
| GenericsType ui = usage[i], di = declaration[i]; |
| if (di.isPlaceholder()) { |
| storeGenericsConnection(connections, di.getName(), ui); |
| } else if (di.isWildcard()) { |
| ClassNode lowerBound = di.getLowerBound(), upperBounds[] = di.getUpperBounds(); |
| if (ui.isWildcard()) { |
| extractGenericsConnections(connections, ui.getLowerBound(), lowerBound); |
| extractGenericsConnections(connections, ui.getUpperBounds(), upperBounds); |
| } else if (!isUnboundedWildcard(di)) { |
| ClassNode boundType = lowerBound != null ? lowerBound : upperBounds[0]; |
| if (boundType.isGenericsPlaceHolder()) { // GROOVY-9998 |
| String placeholderName = boundType.getUnresolvedName(); |
| ui = new GenericsType(ui.getType()); ui.setWildcard(true); |
| storeGenericsConnection(connections, placeholderName, ui); |
| } else { // di like "? super Collection<T>" and ui like "List<Type>" |
| extractGenericsConnections(connections, ui.getType(), boundType); |
| } |
| } |
| } else { |
| extractGenericsConnections(connections, ui.getType(), di.getType()); |
| } |
| } |
| } |
| |
| private static void extractGenericsConnections(final Map<GenericsTypeName, GenericsType> connections, final ClassNode[] usage, final ClassNode[] declaration) { |
| if (usage == null || declaration == null || declaration.length == 0) return; |
| // both have generics |
| for (int i = 0, n = usage.length; i < n; i += 1) { |
| ClassNode ui = usage[i]; |
| ClassNode di = declaration[i]; |
| if (di.isGenericsPlaceHolder()) { |
| storeGenericsConnection(connections, di.getUnresolvedName(), new GenericsType(ui)); |
| } else if (di.isUsingGenerics()) { |
| extractGenericsConnections(connections, ui.getGenericsTypes(), di.getGenericsTypes()); |
| } |
| } |
| } |
| |
| private static void storeGenericsConnection(final Map<GenericsTypeName, GenericsType> connections, final String placeholderName, final GenericsType gt) { |
| //connections.merge(new GenericsTypeName(placeholderName), gt, (gt1, gt2) -> getCombinedGenericsType(gt1, gt2)); |
| connections.put(new GenericsTypeName(placeholderName), gt); |
| } |
| |
| static GenericsType[] getGenericsWithoutArray(final ClassNode type) { |
| if (type.isArray()) return getGenericsWithoutArray(type.getComponentType()); |
| return type.getGenericsTypes(); |
| } |
| |
| static GenericsType[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final GenericsType[] gts) { |
| if (gts == null || spec == null || spec.isEmpty()) return gts; |
| |
| int n = gts.length; |
| if (n == 0) return gts; |
| GenericsType[] newGTs = new GenericsType[n]; |
| for (int i = 0; i < n; i += 1) { |
| newGTs[i] = applyGenericsContext(spec, gts[i]); |
| } |
| return newGTs; |
| } |
| |
| private static GenericsType applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final GenericsType gt) { |
| ClassNode type = gt.getType(); |
| |
| 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(type, applyGenericsContext(spec, gt.getUpperBounds()), applyGenericsContext(spec, gt.getLowerBound())); |
| newGT.setPlaceholder(true); |
| return newGT; |
| } |
| return gt; |
| } |
| |
| if (gt.isWildcard()) { |
| GenericsType newGT = new GenericsType(type, applyGenericsContext(spec, gt.getUpperBounds()), applyGenericsContext(spec, gt.getLowerBound())); |
| newGT.setWildcard(true); |
| return newGT; |
| } |
| |
| ClassNode newType; |
| if (type.isArray()) { |
| newType = applyGenericsContext(spec, type.getComponentType()).makeArray(); |
| } else if (type.getGenericsTypes() == null//type.isGenericsPlaceHolder() |
| && type.getOuterClass() == null) { |
| return gt; |
| } else { |
| newType = type.getPlainNodeReference(); |
| newType.setGenericsPlaceHolder(type.isGenericsPlaceHolder()); |
| newType.setGenericsTypes(applyGenericsContext(spec, type.getGenericsTypes())); |
| |
| // GROOVY-10646: non-static inner class + outer class type parameter |
| if ((type.getModifiers() & Opcodes.ACC_STATIC) == 0) { |
| Optional.ofNullable(type.getOuterClass()) |
| .filter(oc -> oc.getGenericsTypes()!=null) |
| .map(oc -> applyGenericsContext(spec, oc)) |
| .ifPresent(oc -> newType.putNodeMetaData("outer.class", oc)); |
| } |
| } |
| return new GenericsType(newType); |
| } |
| |
| private static boolean hasNonTrivialBounds(final GenericsType gt) { |
| if (gt.isWildcard()) { |
| return true; |
| } |
| if (gt.getLowerBound() != null) { |
| return true; |
| } |
| ClassNode[] upperBounds = gt.getUpperBounds(); |
| if (upperBounds != null) { |
| return (upperBounds.length != 1 || upperBounds[0].isGenericsPlaceHolder() || !isObjectType(upperBounds[0])); |
| } |
| return false; |
| } |
| |
| static ClassNode[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final ClassNode[] types) { |
| if (types == null) return null; |
| final int nTypes = types.length; |
| ClassNode[] newTypes = new ClassNode[nTypes]; |
| for (int i = 0; i < nTypes; i += 1) { |
| newTypes[i] = applyGenericsContext(spec, types[i]); |
| } |
| return newTypes; |
| } |
| |
| static ClassNode applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final ClassNode type) { |
| if (type == null || !isUsingGenericsOrIsArrayUsingGenerics(type)) { |
| return type; |
| } |
| if (type.isArray()) { |
| return applyGenericsContext(spec, type.getComponentType()).makeArray(); |
| } |
| |
| GenericsType[] gt = type.getGenericsTypes(); |
| if (asBoolean(spec)) { |
| gt = applyGenericsContext(spec, gt); |
| } |
| if (!type.isGenericsPlaceHolder()) { // convert Type<T> to Type<...> |
| ClassNode cn = type.getPlainNodeReference(); |
| cn.setGenericsTypes(gt); |
| return cn; |
| } |
| |
| if (!gt[0].isPlaceholder()) { // convert T to Type or Type<...> |
| return getCombinedBoundType(gt[0]); |
| } |
| |
| if (type.getGenericsTypes()[0] != gt[0]) { // convert T to X |
| ClassNode cn = make(gt[0].getName()); |
| cn.setRedirect(gt[0].getType()); |
| cn.setGenericsPlaceHolder(true); |
| cn.setGenericsTypes(gt); |
| return cn; |
| } |
| |
| return type; // nothing to do |
| } |
| |
| static ClassNode getCombinedBoundType(final 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 OBJECT_TYPE; // GROOVY-10328 |
| if (genericsType.getUpperBounds() != null) return genericsType.getUpperBounds()[0]; |
| } |
| return genericsType.getType(); |
| } |
| |
| static GenericsType getCombinedGenericsType(GenericsType gt1, GenericsType gt2) { |
| // GROOVY-9998, GROOVY-10499: unpack "?" that is from "? extends T" |
| if (isUnboundedWildcard(gt1)) gt1 = gt1.getType().asGenericsType(); |
| if (isUnboundedWildcard(gt2)) gt2 = gt2.getType().asGenericsType(); |
| ClassNode cn1 = GenericsUtils.makeClassSafe0(CLASS_Type, gt1); |
| ClassNode cn2 = GenericsUtils.makeClassSafe0(CLASS_Type, gt2); |
| ClassNode lub = lowestUpperBound(cn1, cn2); |
| return lub.getGenericsTypes()[0]; |
| } |
| |
| /** |
| * 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(final ClassNode type) { |
| if (type.isArray()) { |
| return boundUnboundedWildcards(type.getComponentType()).makeArray(); |
| } |
| ClassNode redirect = type.redirect(); |
| if (redirect == null || redirect == type || !isUsingGenericsOrIsArrayUsingGenerics(redirect)) { |
| return type; |
| } |
| ClassNode newType = type.getPlainNodeReference(); |
| newType.setGenericsPlaceHolder(type.isGenericsPlaceHolder()); |
| newType.setGenericsTypes(boundUnboundedWildcards(type.getGenericsTypes(), redirect.getGenericsTypes())); |
| return newType; |
| } |
| |
| private static GenericsType[] boundUnboundedWildcards(final GenericsType[] actual, final GenericsType[] declared) { |
| int n = actual.length; GenericsType[] newTypes = new GenericsType[n]; |
| for (int i = 0; i < n; i += 1) { |
| newTypes[i] = boundUnboundedWildcard(actual[i], declared[i]); |
| } |
| return newTypes; |
| } |
| |
| private static GenericsType boundUnboundedWildcard(final GenericsType actual, final GenericsType declared) { |
| if (!isUnboundedWildcard(actual)) return actual; |
| ClassNode lowerBound = declared.getLowerBound(); |
| ClassNode[] upperBounds = declared.getUpperBounds(); |
| if (lowerBound != null) { |
| assert upperBounds == null; |
| } else if (upperBounds == null) { |
| upperBounds = new ClassNode[]{OBJECT_TYPE}; |
| } else if (declared.isPlaceholder()) { |
| upperBounds = upperBounds.clone(); |
| for (int i = 0, n = upperBounds.length; i < n; i += 1) { |
| // GROOVY-10055, GROOVY-10619: purge self references |
| if (GenericsUtils.extractPlaceholders(upperBounds[i]) |
| .containsKey(new GenericsTypeName(declared.getName()))) |
| upperBounds[i] = upperBounds[i].getPlainNodeReference(); |
| } |
| } |
| GenericsType newType = new GenericsType(makeWithoutCaching("?"), upperBounds, lowerBound); |
| newType.setWildcard(true); |
| return newType; |
| } |
| |
| public static boolean isUnboundedWildcard(final GenericsType gt) { |
| if (gt.isWildcard() && gt.getLowerBound() == null) { |
| ClassNode[] upperBounds = gt.getUpperBounds(); |
| return (upperBounds == null || upperBounds.length == 0 || (upperBounds.length == 1 |
| && isObjectType(upperBounds[0]) && !upperBounds[0].isGenericsPlaceHolder())); |
| } |
| return false; |
| } |
| |
| static Map<GenericsTypeName, GenericsType> extractGenericsParameterMapOfThis(final TypeCheckingContext context) { |
| ClassNode cn = context.getEnclosingClassNode(); |
| MethodNode mn = context.getEnclosingMethod(); |
| // GROOVY-9570: find the innermost class or method |
| if (cn != null && cn.getEnclosingMethod() == mn) { |
| return extractGenericsParameterMapOfThis(cn); |
| } else { |
| return extractGenericsParameterMapOfThis(mn); |
| } |
| } |
| |
| private static Map<GenericsTypeName, GenericsType> extractGenericsParameterMapOfThis(final MethodNode mn) { |
| if (mn == null) return null; |
| |
| Map<GenericsTypeName, GenericsType> map = null; |
| if (!mn.isStatic()) { |
| map = extractGenericsParameterMapOfThis(mn.getDeclaringClass()); |
| } |
| |
| GenericsType[] gts = mn.getGenericsTypes(); |
| if (gts != null) { |
| if (map == null) map = new HashMap<>(); |
| for (GenericsType gt : gts) { |
| assert gt.isPlaceholder(); |
| map.put(new GenericsTypeName(gt.getName()), gt); |
| } |
| } |
| |
| return map; |
| } |
| |
| private static Map<GenericsTypeName, GenericsType> extractGenericsParameterMapOfThis(final ClassNode cn) { |
| if (cn == null) return null; |
| |
| Map<GenericsTypeName, GenericsType> map = null; |
| if ((cn.getModifiers() & Opcodes.ACC_STATIC) == 0) { |
| if (cn.getEnclosingMethod() != null) { |
| map = extractGenericsParameterMapOfThis(cn.getEnclosingMethod()); |
| } else if (cn.getOuterClass() != null) { |
| map = extractGenericsParameterMapOfThis(cn.getOuterClass()); |
| } |
| } |
| |
| if (!(cn instanceof InnerClassNode && ((InnerClassNode) cn).isAnonymous())) { |
| GenericsType[] gts = cn.getGenericsTypes(); |
| if (gts != null) { |
| if (map == null) map = new HashMap<>(); |
| for (GenericsType gt : gts) { |
| assert gt.isPlaceholder(); |
| map.put(new GenericsTypeName(gt.getName()), gt); |
| } |
| } |
| } |
| |
| return map; |
| } |
| |
| /** |
| * 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(final List<MethodNode> methodNodeList, final 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; |
| } |
| |
| /** |
| * @return true if the class node is either a GString or the LUB of String and GString. |
| */ |
| public static boolean isGStringOrGStringStringLUB(final ClassNode node) { |
| return isGStringType(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(final 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(final 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()); |
| } |
| |
| /** |
| * Determines if node is a raw type or references any generics placeholders. |
| */ |
| public static boolean missesGenericsTypes(ClassNode cn) { |
| while (cn.isArray()) cn = cn.getComponentType(); |
| GenericsType[] cnGenerics = cn.getGenericsTypes(); |
| GenericsType[] rnGenerics = cn.redirect().getGenericsTypes(); |
| return cnGenerics == null || cnGenerics.length == 0 ? rnGenerics != null : GenericsUtils.hasUnresolvedGenerics(cn); |
| } |
| |
| /** |
| * 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(final Expression expr, final CompilerConfiguration config) { |
| Expression ce = expr instanceof CastExpression ? ((CastExpression) expr).getExpression() : expr; |
| if (ce instanceof ConstantExpression) { |
| if (expr.getType().equals(ce.getType())) |
| return ((ConstantExpression) ce).getValue(); |
| } else if (ce instanceof ListExpression) { |
| if (expr.getType().isArray() && expr.getType().getComponentType().equals(STRING_TYPE)) |
| return ((ListExpression) ce).getExpressions().stream().map(e -> evaluateExpression(e, config)).toArray(String[]::new); |
| } |
| |
| String className = "Expression$" + UUID.randomUUID().toString().replace('-', '$'); |
| ClassNode simpleClass = new ClassNode(className, Opcodes.ACC_PUBLIC, OBJECT_TYPE); |
| addGeneratedMethod(simpleClass, "eval", Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new ReturnStatement(expr)); |
| |
| // adjust configuration so class can be inspected by this JVM |
| CompilerConfiguration cc = new CompilerConfiguration(config); |
| cc.setPreviewFeatures(false); // unlikely to be required by expression |
| cc.setTargetBytecode(CompilerConfiguration.DEFAULT.getTargetBytecode()); |
| |
| CompilationUnit cu = new CompilationUnit(cc); |
| try { |
| cu.addClassNode(simpleClass); |
| 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 (ReflectiveOperationException e) { |
| throw new GroovyBugError(e); |
| } |
| } finally { |
| closeQuietly(cu.getClassLoader()); |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| @Deprecated |
| public static Set<ClassNode> collectAllInterfaces(final ClassNode node) { |
| return GeneralUtils.getInterfacesAndSuperInterfaces(node); |
| } |
| |
| @Deprecated |
| public static ClassNode getCorrectedClassNode(final ClassNode cn, final ClassNode sc, final boolean completed) { |
| if (completed && GenericsUtils.hasUnresolvedGenerics(cn)) return sc.getPlainNodeReference(); |
| return GenericsUtils.correctToGenericsSpecRecurse(GenericsUtils.createGenericsSpec(cn), sc); |
| } |
| |
| /** |
| * 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<Foo> where Foo is a class would return true, but the class |
| * node for Class<?> 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(final ClassNode classNode) { |
| GenericsType[] genericsTypes = classNode.getGenericsTypes(); |
| return isClassType(classNode) |
| && classNode.isUsingGenerics() |
| && genericsTypes != null |
| && !genericsTypes[0].isPlaceholder() |
| && !genericsTypes[0].isWildcard(); |
| } |
| |
| public static List<MethodNode> findSetters(final ClassNode cn, final String setterName, final boolean voidOnly) { |
| List<MethodNode> result = new ArrayList<>(); |
| if (!cn.isInterface()) { |
| for (MethodNode method : cn.getMethods(setterName)) { |
| if (isSetter(method, voidOnly)) result.add(method); |
| } |
| } |
| for (ClassNode in : cn.getAllInterfaces()) { |
| for (MethodNode method : in.getDeclaredMethods(setterName)) { |
| if (isSetter(method, voidOnly)) result.add(method); |
| } |
| } |
| return result; |
| } |
| |
| private static boolean isSetter(final MethodNode mn, final boolean voidOnly) { |
| return (!voidOnly || mn.isVoidMethod()) && mn.getParameters().length == 1; |
| } |
| |
| public static ClassNode isTraitSelf(final 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; |
| } |
| |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * A DGM-like class which adds support for method calls which are handled |
| * specifically by the Groovy compiler. |
| */ |
| public static class ObjectArrayStaticTypesHelper { |
| public static <T> T getAt(final T[] array, final int index) { |
| return array != null ? array[index] : null; |
| } |
| public static <T, U extends T> void putAt(final T[] array, final int index, final U value) { |
| if (array != null) { |
| array[index] = value; |
| } |
| } |
| } |
| |
| public static class BooleanArrayStaticTypesHelper { |
| public static Boolean getAt(final boolean[] array, final int index) { |
| return array != null ? array[index] : null; |
| } |
| public static void putAt(final boolean[] array, final int index, final boolean value) { |
| if (array != null) { |
| array[index] = value; |
| } |
| } |
| } |
| |
| public static class CharArrayStaticTypesHelper { |
| public static Character getAt(final char[] array, final int index) { |
| return array != null ? array[index] : null; |
| } |
| public static void putAt(final char[] array, final int index, final char value) { |
| if (array != null) { |
| array[index] = value; |
| } |
| } |
| } |
| |
| public static class ByteArrayStaticTypesHelper { |
| public static Byte getAt(final byte[] array, final int index) { |
| return array != null ? array[index] : null; |
| } |
| public static void putAt(final byte[] array, final int index, final byte value) { |
| if (array != null) { |
| array[index] = value; |
| } |
| } |
| } |
| |
| public static class ShortArrayStaticTypesHelper { |
| public static Short getAt(final short[] array, final int index) { |
| return array != null ? array[index] : null; |
| } |
| public static void putAt(final short[] array, final int index, final short value) { |
| if (array != null) { |
| array[index] = value; |
| } |
| } |
| } |
| |
| public static class IntArrayStaticTypesHelper { |
| public static Integer getAt(final int[] array, final int index) { |
| return array != null ? array[index] : null; |
| } |
| public static void putAt(final int[] array, final int index, final int value) { |
| if (array != null) { |
| array[index] = value; |
| } |
| } |
| } |
| |
| public static class LongArrayStaticTypesHelper { |
| public static Long getAt(final long[] array, final int index) { |
| return array != null ? array[index] : null; |
| } |
| public static void putAt(final long[] array, final int index, final long value) { |
| if (array != null) { |
| array[index] = value; |
| } |
| } |
| } |
| |
| public static class FloatArrayStaticTypesHelper { |
| public static Float getAt(final float[] array, final int index) { |
| return array != null ? array[index] : null; |
| } |
| public static void putAt(final float[] array, final int index, final float value) { |
| if (array != null) { |
| array[index] = value; |
| } |
| } |
| } |
| |
| public static class DoubleArrayStaticTypesHelper { |
| public static Double getAt(final double[] array, final int index) { |
| return array != null ? array[index] : null; |
| } |
| public static void putAt(final double[] array, final int index, final double value) { |
| if (array != null) { |
| array[index] = value; |
| } |
| } |
| } |
| } |