| /* |
| * 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.ast.tools; |
| |
| import groovy.lang.Tuple2; |
| import groovy.transform.stc.IncorrectTypeHintException; |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.ast.ASTNode; |
| import org.codehaus.groovy.ast.ClassHelper; |
| import org.codehaus.groovy.ast.ClassNode; |
| import org.codehaus.groovy.ast.GenericsType; |
| import org.codehaus.groovy.ast.MethodNode; |
| import org.codehaus.groovy.ast.ModuleNode; |
| import org.codehaus.groovy.ast.Parameter; |
| import org.codehaus.groovy.ast.decompiled.DecompiledClassNode; |
| import org.codehaus.groovy.ast.expr.DeclarationExpression; |
| import org.codehaus.groovy.ast.stmt.EmptyStatement; |
| import org.codehaus.groovy.ast.stmt.ExpressionStatement; |
| import org.codehaus.groovy.control.CompilationUnit; |
| import org.codehaus.groovy.control.ParserPlugin; |
| import org.codehaus.groovy.control.ResolveVisitor; |
| import org.codehaus.groovy.control.SourceUnit; |
| import org.codehaus.groovy.runtime.memoize.ConcurrentSoftCache; |
| import org.codehaus.groovy.runtime.memoize.EvictableCache; |
| |
| import java.lang.ref.SoftReference; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.function.Function; |
| import java.util.function.Predicate; |
| |
| import static org.apache.groovy.util.SystemUtil.getSystemPropertySafe; |
| import static org.codehaus.groovy.runtime.DefaultGroovyMethods.plus; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf; |
| import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUnboundedWildcard; |
| |
| /** |
| * Utility methods to deal with generic types. |
| */ |
| public class GenericsUtils { |
| public static final GenericsType[] EMPTY_GENERICS_ARRAY = GenericsType.EMPTY_ARRAY; |
| public static final String JAVA_LANG_OBJECT = ClassHelper.OBJECT; |
| |
| /** |
| * Given a parameterized type and a generic type information, aligns actual type parameters. For example, if a |
| * class uses generic type <pre><T,U,V></pre> (redirectGenericTypes), is used with actual type parameters |
| * <pre><java.lang.String, U,V></pre>, then a class or interface using generic types <pre><T,V></pre> |
| * will be aligned to <pre><java.lang.String,V></pre> |
| * |
| * @param redirectGenericTypes the type arguments or the redirect class node |
| * @param parameterizedTypes the actual type arguments used on this class node |
| * @param alignmentTarget the generic type arguments to which we want to align to |
| * @return aligned type arguments |
| * @deprecated You shouldn't call this method because it is inherently unreliable |
| */ |
| @Deprecated(forRemoval = true, since = "2.3.0") |
| public static GenericsType[] alignGenericTypes(final GenericsType[] redirectGenericTypes, final GenericsType[] parameterizedTypes, final GenericsType[] alignmentTarget) { |
| if (alignmentTarget == null) return EMPTY_GENERICS_ARRAY; |
| if (parameterizedTypes == null || parameterizedTypes.length == 0) return alignmentTarget; |
| GenericsType[] generics = new GenericsType[alignmentTarget.length]; |
| for (int i = 0, scgtLength = alignmentTarget.length; i < scgtLength; i++) { |
| final GenericsType currentTarget = alignmentTarget[i]; |
| GenericsType match = null; |
| if (redirectGenericTypes != null) { |
| for (int j = 0; j < redirectGenericTypes.length && match == null; j++) { |
| GenericsType redirectGenericType = redirectGenericTypes[j]; |
| if (redirectGenericType.isCompatibleWith(currentTarget.getType())) { |
| if (currentTarget.isPlaceholder() && redirectGenericType.isPlaceholder() && !currentTarget.getName().equals(redirectGenericType.getName())) { |
| // check if there's a potential better match |
| boolean skip = false; |
| for (int k = j + 1; k < redirectGenericTypes.length && !skip; k++) { |
| GenericsType ogt = redirectGenericTypes[k]; |
| if (ogt.isPlaceholder() && ogt.isCompatibleWith(currentTarget.getType()) && ogt.getName().equals(currentTarget.getName())) { |
| skip = true; |
| } |
| } |
| if (skip) continue; |
| } |
| match = parameterizedTypes[j]; |
| if (currentTarget.isWildcard()) { |
| // if alignment target is a wildcard type |
| // then we must make best effort to return a parameterized |
| // wildcard |
| ClassNode lower = currentTarget.getLowerBound() != null ? match.getType() : null; |
| ClassNode[] currentUpper = currentTarget.getUpperBounds(); |
| ClassNode[] upper = currentUpper != null ? new ClassNode[currentUpper.length] : null; |
| if (upper != null) { |
| for (int k = 0; k < upper.length; k++) { |
| upper[k] = currentUpper[k].isGenericsPlaceHolder() ? match.getType() : currentUpper[k]; |
| } |
| } |
| match = new GenericsType(ClassHelper.makeWithoutCaching("?"), upper, lower); |
| match.setWildcard(true); |
| } |
| } |
| } |
| } |
| if (match == null) { |
| match = currentTarget; |
| } |
| generics[i] = match; |
| } |
| return generics; |
| } |
| |
| /** |
| * Generates a wildcard generic type in order to be used for checks against |
| * class nodes. See {@link GenericsType#isCompatibleWith(ClassNode)}. |
| */ |
| public static GenericsType buildWildcardType(final ClassNode... upperBounds) { |
| GenericsType gt = new GenericsType(ClassHelper.makeWithoutCaching("?"), upperBounds, null); |
| gt.setWildcard(true); |
| return gt; |
| } |
| |
| /** |
| * Returns the type parameter/argument relationships of the specified type. |
| * |
| * @param type the class node to check |
| */ |
| public static Map<GenericsType.GenericsTypeName, GenericsType> extractPlaceholders(final ClassNode type) { |
| Map<GenericsType.GenericsTypeName, GenericsType> placeholders = new HashMap<>(); |
| extractPlaceholders(type, placeholders); |
| return placeholders; |
| } |
| |
| /** |
| * Populates the supplied map with the type parameter/argument relationships |
| * of the specified type. |
| * |
| * @param type the class node to check |
| * @param placeholders the generics type information collector |
| */ |
| public static void extractPlaceholders(final ClassNode type, final Map<GenericsType.GenericsTypeName, GenericsType> placeholders) { |
| if (type == null) return; |
| |
| if (type.isArray()) { |
| extractPlaceholders(type.getComponentType(), placeholders); |
| return; |
| } |
| |
| if (!type.isUsingGenerics() || !type.isRedirectNode()) return; |
| GenericsType[] genericsTypes = type.getGenericsTypes(); int n; |
| if (genericsTypes == null || (n = genericsTypes.length) == 0) return; |
| |
| // GROOVY-8609, GROOVY-10067, etc. |
| if (type.isGenericsPlaceHolder()) { |
| GenericsType gt = genericsTypes[0]; |
| placeholders.putIfAbsent(new GenericsType.GenericsTypeName(gt.getName()), gt); |
| return; |
| } |
| |
| GenericsType[] redirectGenericsTypes = type.redirect().getGenericsTypes(); |
| if (redirectGenericsTypes == null) { |
| redirectGenericsTypes = genericsTypes; |
| } else if (redirectGenericsTypes.length != n) { |
| throw new GroovyBugError("Expected earlier checking to detect generics parameter arity mismatch" + |
| "\nExpected: " + type.getName() + toGenericTypesString(redirectGenericsTypes) + |
| "\nSupplied: " + type.getName() + toGenericTypesString(genericsTypes)); |
| } |
| |
| List<GenericsType> typeArguments = new ArrayList<>(n); |
| for (int i = 0; i < n; i += 1) { |
| GenericsType rgt = redirectGenericsTypes[i]; |
| if (rgt.isPlaceholder()) { // type parameter |
| GenericsType typeArgument = genericsTypes[i]; |
| placeholders.computeIfAbsent(new GenericsType.GenericsTypeName(rgt.getName()), x -> { |
| typeArguments.add(typeArgument); |
| return typeArgument; |
| }); |
| } |
| } |
| |
| // examine non-placeholder type args |
| for (GenericsType gt : typeArguments) { |
| if (gt.isWildcard()) { |
| ClassNode lowerBound = gt.getLowerBound(); |
| if (lowerBound != null) { |
| extractPlaceholders(lowerBound, placeholders); |
| } else { |
| ClassNode[] upperBounds = gt.getUpperBounds(); |
| if (upperBounds != null) { |
| for (ClassNode upperBound : upperBounds) { |
| extractPlaceholders(upperBound, placeholders); |
| } |
| } |
| } |
| } else if (!gt.isPlaceholder()) { |
| extractPlaceholders(gt.getType(), placeholders); |
| } |
| } |
| } |
| |
| public static String toGenericTypesString(GenericsType[] genericsTypes) { |
| if (genericsTypes == null) return ""; |
| StringBuilder sb = new StringBuilder("<"); |
| for (int i = 0, n = genericsTypes.length; i < n; i++) { |
| sb.append(genericsTypes[i].toString()); |
| if (i < n - 1) { |
| sb.append(","); |
| } |
| } |
| sb.append("> "); |
| return sb.toString(); |
| } |
| |
| /** |
| * Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()} |
| * or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type |
| * arguments. This method allows returning a parameterized interface given the parameterized class |
| * node which implements this interface. |
| * |
| * @param hint the class node where generics types are parameterized |
| * @param target the interface we want to parameterize generics types |
| * @return a parameterized interface class node |
| * @deprecated Use #parameterizeType instead |
| */ |
| @Deprecated(forRemoval = true, since = "2.5.0") |
| public static ClassNode parameterizeInterfaceGenerics(final ClassNode hint, final ClassNode target) { |
| return parameterizeType(hint, target); |
| } |
| |
| /** |
| * Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()} |
| * or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type |
| * arguments. This method allows returning a parameterized interface given the parameterized class |
| * node which implements this interface. |
| * |
| * @param hint the ClassNode where generics types are parameterized |
| * @param target the interface we want to parameterize generics types |
| * @return a parameterized interface ClassNode |
| */ |
| public static ClassNode parameterizeType(ClassNode hint, final ClassNode target) { |
| if (hint.isArray()) { |
| if (target.isArray()) { |
| return parameterizeType(hint.getComponentType(), target.getComponentType()).makeArray(); |
| } |
| return target; |
| } |
| if (hint.isGenericsPlaceHolder()) { |
| ClassNode bound = hint.redirect(); |
| return parameterizeType(bound, target); |
| } |
| if (target.redirect().getGenericsTypes() == null) { |
| return target; |
| } |
| |
| ClassNode cn = target; |
| Map<String, ClassNode> gt; |
| |
| // relationship may be reversed for cases like "Iterable<String> x = []" |
| if (!cn.equals(hint) && implementsInterfaceOrIsSubclassOf(target, hint)) { |
| do { // walk target type hierarchy towards hint |
| cn = ClassHelper.getNextSuperClass(cn, hint); |
| if (hasUnresolvedGenerics(cn)) { |
| gt = createGenericsSpec(hint); |
| extractSuperClassGenerics(hint, cn, gt); |
| cn = correctToGenericsSpecRecurse(gt, cn); |
| } |
| } while (!cn.equals(hint)); |
| |
| hint = cn; |
| } |
| |
| cn = target.redirect(); |
| gt = createGenericsSpec(hint); |
| gt = createGenericsSpec(cn, gt); |
| extractSuperClassGenerics(hint, cn, gt); |
| return correctToGenericsSpecRecurse(gt, cn); |
| } |
| |
| public static ClassNode nonGeneric(final ClassNode type) { |
| int dims = 0; |
| ClassNode temp = type; |
| while (temp.isArray()) { dims += 1; |
| temp = temp.getComponentType(); |
| } |
| if (temp instanceof DecompiledClassNode // GROOVY-10461: check without resolving supers |
| ? ((DecompiledClassNode) temp).isParameterized() : temp.isUsingGenerics()) { |
| ClassNode result = ClassHelper.makeWithoutCaching(temp.getName()); |
| result.setRedirect(temp); |
| result.setGenericsTypes(null); |
| result.setUsingGenerics(false); |
| while (dims > 0) { dims -= 1; |
| result = result.makeArray(); |
| } |
| return result; |
| } |
| return type; |
| } |
| |
| public static ClassNode newClass(ClassNode type) { |
| return type.getPlainNodeReference(); |
| } |
| |
| public static ClassNode makeClassSafe(Class klass) { |
| return makeClassSafeWithGenerics(ClassHelper.make(klass)); |
| } |
| |
| public static ClassNode makeClassSafeWithGenerics(Class klass, ClassNode genericsType) { |
| GenericsType[] genericsTypes = new GenericsType[1]; |
| genericsTypes[0] = new GenericsType(genericsType); |
| return makeClassSafeWithGenerics(ClassHelper.make(klass), genericsTypes); |
| } |
| |
| public static ClassNode makeClassSafe0(ClassNode type, GenericsType... genericTypes) { |
| ClassNode plainNodeReference = newClass(type); |
| if (genericTypes != null && genericTypes.length > 0) { |
| plainNodeReference.setGenericsTypes(genericTypes); |
| if (type.isGenericsPlaceHolder()) plainNodeReference.setGenericsPlaceHolder(true); |
| } |
| return plainNodeReference; |
| } |
| |
| public static ClassNode makeClassSafeWithGenerics(ClassNode type, GenericsType... genericTypes) { |
| if (type.isArray()) { |
| return makeClassSafeWithGenerics(type.getComponentType(), genericTypes).makeArray(); |
| } |
| int nTypes = (genericTypes == null ? 0 : genericTypes.length); |
| GenericsType[] gTypes; |
| if (nTypes == 0) { |
| gTypes = EMPTY_GENERICS_ARRAY; |
| } else { |
| gTypes = new GenericsType[nTypes]; |
| System.arraycopy(genericTypes, 0, gTypes, 0, nTypes); |
| } |
| return makeClassSafe0(type, gTypes); |
| } |
| |
| public static MethodNode correctToGenericsSpec(Map<String, ClassNode> genericsSpec, MethodNode mn) { |
| if (genericsSpec == null) return mn; |
| if (mn.getGenericsTypes() != null) genericsSpec = addMethodGenerics(mn, genericsSpec); |
| ClassNode returnType = correctToGenericsSpecRecurse(genericsSpec, mn.getReturnType()); |
| Parameter[] oldParameters = mn.getParameters(); int nParameters= oldParameters.length; |
| Parameter[] newParameters = new Parameter[nParameters]; |
| for (int i = 0; i < nParameters; i += 1) { |
| Parameter oldParameter = oldParameters[i]; |
| newParameters[i] = new Parameter(correctToGenericsSpecRecurse(genericsSpec, oldParameter.getType()), oldParameter.getName(), oldParameter.getInitialExpression()); |
| } |
| MethodNode newMethod = new MethodNode(mn.getName(), mn.getModifiers(), returnType, newParameters, mn.getExceptions(), mn.getCode()); |
| newMethod.setGenericsTypes(mn.getGenericsTypes()); |
| return newMethod; |
| } |
| |
| public static ClassNode correctToGenericsSpecRecurse(Map<String, ClassNode> genericsSpec, ClassNode type) { |
| return correctToGenericsSpecRecurse(genericsSpec, type, Collections.emptyList()); |
| } |
| |
| /** |
| * @since 2.4.1 |
| */ |
| public static ClassNode[] correctToGenericsSpecRecurse(Map<String, ClassNode> genericsSpec, ClassNode[] types) { |
| if (types == null || types.length == 1) return types; |
| ClassNode[] newTypes = new ClassNode[types.length]; |
| boolean modified = false; |
| for (int i = 0; i < types.length; i++) { |
| newTypes[i] = correctToGenericsSpecRecurse(genericsSpec, types[i], Collections.emptyList()); |
| modified = modified || (types[i] != newTypes[i]); |
| } |
| if (!modified) return types; |
| return newTypes; |
| } |
| |
| public static ClassNode correctToGenericsSpecRecurse(Map<String, ClassNode> genericsSpec, ClassNode type, List<String> exclusions) { |
| if (type.isArray()) { |
| return correctToGenericsSpecRecurse(genericsSpec, type.getComponentType(), exclusions).makeArray(); |
| } |
| String name = type.getUnresolvedName(); |
| if (type.isGenericsPlaceHolder() && !exclusions.contains(name)) { |
| exclusions = plus(exclusions, name); // GROOVY-7722 |
| type = genericsSpec.get(name); |
| if (type != null && type.isGenericsPlaceHolder()) { |
| if (type.getGenericsTypes() == null) { |
| ClassNode placeholder = ClassHelper.makeWithoutCaching(type.getUnresolvedName()); |
| placeholder.setGenericsPlaceHolder(true); |
| return makeClassSafeWithGenerics(type, new GenericsType(placeholder)); |
| } else if (!name.equals(type.getUnresolvedName())) { |
| return correctToGenericsSpecRecurse(genericsSpec, type, exclusions); |
| } |
| } |
| } |
| if (type == null) type = ClassHelper.OBJECT_TYPE.getPlainNodeReference(); |
| GenericsType[] oldgTypes = type.getGenericsTypes(); |
| GenericsType[] newgTypes = EMPTY_GENERICS_ARRAY; |
| if (oldgTypes != null) { |
| newgTypes = new GenericsType[oldgTypes.length]; |
| for (int i = 0; i < newgTypes.length; i++) { |
| GenericsType oldgType = oldgTypes[i]; |
| if (oldgType.isWildcard()) { |
| ClassNode[] oldUpper = oldgType.getUpperBounds(); |
| ClassNode[] upper = null; |
| if (oldUpper != null) { |
| // correct "? extends T" or "? extends T & I" |
| upper = new ClassNode[oldUpper.length]; |
| for (int j = 0; j < oldUpper.length; j++) { |
| upper[j] = correctToGenericsSpecRecurse(genericsSpec, oldUpper[j], exclusions); |
| } |
| } |
| ClassNode oldLower = oldgType.getLowerBound(); |
| ClassNode lower = null; |
| if (oldLower != null) { |
| // correct "? super T" |
| lower = correctToGenericsSpecRecurse(genericsSpec, oldLower, exclusions); |
| } |
| GenericsType fixed = new GenericsType(oldgType.getType(), upper, lower); |
| fixed.setWildcard(true); |
| newgTypes[i] = fixed; |
| } else if (oldgType.isPlaceholder()) { |
| // correct "T" |
| newgTypes[i] = new GenericsType(genericsSpec.getOrDefault(oldgType.getName(), ClassHelper.OBJECT_TYPE)); |
| } else { |
| // correct "List<T>", etc. |
| newgTypes[i] = new GenericsType(correctToGenericsSpecRecurse(genericsSpec, correctToGenericsSpec(genericsSpec, oldgType), exclusions)); |
| } |
| } |
| } |
| return makeClassSafeWithGenerics(type, newgTypes); |
| } |
| |
| public static ClassNode correctToGenericsSpec(final Map<String, ClassNode> genericsSpec, final GenericsType type) { |
| ClassNode cn = null; |
| if (type.isPlaceholder()) { |
| String name = type.getName(); |
| if (name.charAt(0) != '#') // |
| cn = genericsSpec.get(name); |
| } |
| else if (type.isWildcard()) { |
| if (type.getUpperBounds() != null) |
| cn = type.getUpperBounds()[0]; // GROOVY-9891 |
| } |
| if (cn == null) { |
| cn = type.getType(); |
| } |
| return cn; |
| } |
| |
| public static ClassNode correctToGenericsSpec(final Map<String, ClassNode> genericsSpec, ClassNode type) { |
| if (type.isArray()) { |
| return correctToGenericsSpec(genericsSpec, type.getComponentType()).makeArray(); |
| } |
| if (type.isGenericsPlaceHolder() && type.getGenericsTypes() != null) { |
| String name = type.getGenericsTypes()[0].getName(); |
| type = genericsSpec.get(name); |
| if (type != null && type.isGenericsPlaceHolder() |
| && !name.equals(type.getUnresolvedName())) { |
| return correctToGenericsSpec(genericsSpec, type); |
| } |
| } |
| return type != null ? type : ClassHelper.OBJECT_TYPE.getPlainNodeReference(); |
| } |
| |
| public static Map<String, ClassNode> createGenericsSpec(final ClassNode type) { |
| return createGenericsSpec(type, Collections.emptyMap()); |
| } |
| |
| public static Map<String, ClassNode> createGenericsSpec(final ClassNode type, final Map<String, ClassNode> oldSpec) { |
| // Example: |
| // abstract class A<X,Y,Z> { ... } |
| // class C<T extends Number> extends A<T,Object,String> { } |
| // the type "A<T,Object,String> -> A<X,Y,Z>" will produce [X:Number,Y:Object,Z:String] |
| |
| ClassNode oc = type.getNodeMetaData("outer.class"); // GROOVY-10646: outer class type parameters |
| Map<String, ClassNode> newSpec = oc != null ? createGenericsSpec(oc, oldSpec) : new HashMap<>(); |
| GenericsType[] gt = type.getGenericsTypes(), rgt = type.redirect().getGenericsTypes(); |
| if (gt != null && rgt != null) { |
| for (int i = 0, n = gt.length; i < n; i += 1) { |
| newSpec.put(rgt[i].getName(), correctToGenericsSpec(oldSpec, gt[i])); |
| } |
| } |
| return newSpec; |
| } |
| |
| public static Map<String, ClassNode> addMethodGenerics(final MethodNode node, final Map<String, ClassNode> oldSpec) { |
| Map<String, ClassNode> newSpec = new HashMap<>(oldSpec); |
| GenericsType[] tps = node.getGenericsTypes(); |
| if (tps != null) { |
| for (GenericsType tp : tps) { |
| String name = tp.getName(); |
| ClassNode type = tp.getType(); |
| ClassNode redirect; |
| if (tp.getUpperBounds() != null) { |
| redirect = tp.getUpperBounds()[0]; |
| } else { |
| redirect = ClassHelper.OBJECT_TYPE; |
| } |
| if (redirect.isGenericsPlaceHolder()) { |
| // "T extends U (& Face)*" |
| type = redirect; |
| } else { |
| // "T" or "T extends Type (& Face)*" |
| type = ClassHelper.makeWithoutCaching(name); |
| type.setGenericsPlaceHolder(true); |
| type.setRedirect(redirect); |
| } |
| newSpec.put(name, type); |
| } |
| } |
| return newSpec; |
| } |
| |
| // TODO: this is very similar to StaticTypesCheckingSupport#extractGenericsConnections, using ClassNode instead of GenericsType |
| public static void extractSuperClassGenerics(final ClassNode type, final ClassNode target, final Map<String, ClassNode> spec) { |
| if (target == null || target == type) return; |
| if (target.isGenericsPlaceHolder()) { |
| spec.put(target.getUnresolvedName(), type); |
| } else if (type.isArray() && target.isArray()) { |
| extractSuperClassGenerics(type.getComponentType(), target.getComponentType(), spec); |
| } else if (type.isArray() && target.getName().equals(JAVA_LANG_OBJECT)) { |
| // Object is the superclass of an array, but no generics are involved |
| } else if (type.equals(target) || !implementsInterfaceOrIsSubclassOf(type, target)) { |
| extractSuperClassGenerics(type.getGenericsTypes(), target.getGenericsTypes(), spec); |
| } else { |
| ClassNode superClass = getSuperClass(type, target); |
| if (superClass != null) { |
| if (hasUnresolvedGenerics(superClass)) { |
| GenericsType[] tp = type.redirect().getGenericsTypes(); |
| if (tp != null) { |
| GenericsType[] ta = type.getGenericsTypes(); |
| boolean noTypeArguments = ta == null || ta.length == 0 || !type.isRedirectNode(); |
| Map<String, ClassNode> genericsSpec = new HashMap<>(); |
| for (int i = 0, n = tp.length; i < n; i += 1) { |
| ClassNode cn; |
| if (noTypeArguments || isUnboundedWildcard(ta[i])) { // GROOVY-10651 |
| GenericsType gt = tp[i]; |
| cn = gt.getUpperBounds() != null ? gt.getUpperBounds()[0] : gt.getType().redirect(); |
| } else { |
| GenericsType gt = ta[i]; |
| cn = gt.isWildcard() && gt.getUpperBounds() != null ? gt.getUpperBounds()[0] : gt.getType(); |
| } |
| genericsSpec.put(tp[i].getName(), cn); |
| } |
| superClass = correctToGenericsSpecRecurse(genericsSpec, superClass); |
| } |
| } |
| extractSuperClassGenerics(superClass, target, spec); |
| } else { |
| // if we reach here, we have an unhandled case |
| throw new GroovyBugError("The type " + type + " seems not to normally extend " + target + ". Sorry, I cannot handle this."); |
| } |
| } |
| } |
| |
| public static ClassNode getSuperClass(final ClassNode type, final ClassNode target) { |
| return ClassHelper.getNextSuperClass(ClassHelper.isPrimitiveType(type) ? ClassHelper.getWrapper(type) : type, target); |
| } |
| |
| private static void extractSuperClassGenerics(final GenericsType[] usage, final GenericsType[] declaration, final Map<String, ClassNode> spec) { |
| // if declaration does not provide generics, there is no connection to make |
| if (declaration == null || declaration.length == 0) return; |
| |
| // if usage is a raw type, remove type parameters from spec |
| if (usage == null) { |
| for (GenericsType dt : declaration) { |
| String name = dt.getName(); |
| ClassNode type = spec.get(name); |
| if (type != null && type.isGenericsPlaceHolder() |
| && type.getUnresolvedName().equals(name)) { |
| type = type.asGenericsType().getUpperBounds()[0]; |
| spec.put(name, type); |
| } |
| } |
| return; |
| } |
| |
| if (usage.length != declaration.length) return; |
| |
| for (int i = 0, n = usage.length; i < n; i += 1) { |
| GenericsType ui = usage[i]; |
| GenericsType di = declaration[i]; |
| if (di.isPlaceholder()) { |
| spec.put(di.getName(), ui.getType()); |
| } else if (di.isWildcard()) { |
| if (ui.isWildcard()) { |
| extractSuperClassGenerics(ui.getLowerBound(), di.getLowerBound(), spec); |
| extractSuperClassGenerics(ui.getUpperBounds(), di.getUpperBounds(), spec); |
| } else { |
| ClassNode cu = ui.getType(); |
| extractSuperClassGenerics(cu, di.getLowerBound(), spec); |
| ClassNode[] upperBounds = di.getUpperBounds(); |
| if (upperBounds != null) { |
| for (ClassNode cn : upperBounds) { |
| extractSuperClassGenerics(cu, cn, spec); |
| } |
| } |
| } |
| } else { |
| extractSuperClassGenerics(ui.getType(), di.getType(), spec); |
| } |
| } |
| } |
| |
| private static void extractSuperClassGenerics(ClassNode[] usage, ClassNode[] declaration, Map<String, ClassNode> spec) { |
| if (usage == null || declaration == null || declaration.length == 0) return; |
| // both have generics |
| for (int i = 0; i < usage.length; i++) { |
| ClassNode ui = usage[i]; |
| ClassNode di = declaration[i]; |
| if (di.isGenericsPlaceHolder()) { |
| spec.put(di.getGenericsTypes()[0].getName(), di); |
| } else if (di.isUsingGenerics()) { |
| extractSuperClassGenerics(ui.getGenericsTypes(), di.getGenericsTypes(), spec); |
| } |
| } |
| } |
| |
| public static ClassNode[] parseClassNodesFromString(final String option, final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final MethodNode mn, final ASTNode usage) { |
| try { |
| ModuleNode moduleNode = ParserPlugin.buildAST("Dummy<" + option + "> dummy;", compilationUnit.getConfiguration(), compilationUnit.getClassLoader(), null); |
| DeclarationExpression dummyDeclaration = (DeclarationExpression) ((ExpressionStatement) moduleNode.getStatementBlock().getStatements().get(0)).getExpression(); |
| |
| // the returned node is DummyNode<Param1, Param2, Param3, ...) |
| ClassNode dummyNode = dummyDeclaration.getLeftExpression().getType(); |
| GenericsType[] dummyNodeGenericsTypes = dummyNode.getGenericsTypes(); |
| if (dummyNodeGenericsTypes != null) { |
| int n = dummyNodeGenericsTypes.length; |
| ClassNode[] signature = new ClassNode[n]; |
| for (int i = 0; i < n; i += 1) { |
| GenericsType genericsType = dummyNodeGenericsTypes[i]; |
| signature[i] = genericsType.isWildcard() ? ClassHelper.dynamicType() |
| : resolveClassNode(sourceUnit, compilationUnit, mn, usage, genericsType.getType()); |
| } |
| return signature; |
| } |
| } catch (Exception | LinkageError e) { |
| sourceUnit.addError(new IncorrectTypeHintException(mn, e, usage.getLineNumber(), usage.getColumnNumber())); |
| } |
| return null; |
| } |
| |
| private static ClassNode resolveClassNode(final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final MethodNode mn, final ASTNode usage, final ClassNode parsedNode) { |
| ClassNode dummyClass = new ClassNode("dummy", 0, ClassHelper.OBJECT_TYPE); |
| dummyClass.setModule(new ModuleNode(sourceUnit)); |
| dummyClass.setGenericsTypes(mn.getDeclaringClass().getGenericsTypes()); |
| MethodNode dummyMN = new MethodNode( |
| "dummy", |
| 0, |
| parsedNode, |
| Parameter.EMPTY_ARRAY, |
| ClassNode.EMPTY_ARRAY, |
| EmptyStatement.INSTANCE |
| ); |
| dummyMN.setGenericsTypes(mn.getGenericsTypes()); |
| dummyClass.addMethod(dummyMN); |
| ResolveVisitor visitor = new ResolveVisitor(compilationUnit) { |
| @Override |
| public void addError(final String msg, final ASTNode expr) { |
| sourceUnit.addError(new IncorrectTypeHintException(mn, msg, usage.getLineNumber(), usage.getColumnNumber())); |
| } |
| }; |
| visitor.startResolving(dummyClass, sourceUnit); |
| return dummyMN.getReturnType(); |
| } |
| |
| /** |
| * Transforms generics types from an old context to a new context using the |
| * given spec. This method assumes all generics types will be placeholders. |
| * WARNING: The resulting generics types may or may not be placeholders |
| * after the transformation. |
| * |
| * @param genericsSpec the generics context information spec |
| * @param oldPlaceHolders the old placeholders |
| * @return the new generics types |
| */ |
| public static GenericsType[] applyGenericsContextToPlaceHolders(Map<String, ClassNode> genericsSpec, GenericsType[] oldPlaceHolders) { |
| if (oldPlaceHolders == null || oldPlaceHolders.length == 0) return oldPlaceHolders; |
| if (genericsSpec.isEmpty()) return oldPlaceHolders; |
| GenericsType[] newTypes = new GenericsType[oldPlaceHolders.length]; |
| for (int i = 0; i < oldPlaceHolders.length; i++) { |
| GenericsType old = oldPlaceHolders[i]; |
| if (!old.isPlaceholder()) |
| throw new GroovyBugError("Given generics type " + old + " must be a placeholder!"); |
| ClassNode fromSpec = genericsSpec.get(old.getName()); |
| if (fromSpec != null) { |
| newTypes[i] = fromSpec.asGenericsType(); |
| } else { |
| ClassNode[] upper = old.getUpperBounds(); |
| ClassNode[] newUpper = upper; |
| if (upper != null && upper.length > 0) { |
| ClassNode[] upperCorrected = new ClassNode[upper.length]; |
| for (ClassNode classNode : upper) { |
| upperCorrected[i] = correctToGenericsSpecRecurse(genericsSpec, classNode); |
| } |
| upper = upperCorrected; |
| } |
| ClassNode lower = old.getLowerBound(); |
| ClassNode newLower = correctToGenericsSpecRecurse(genericsSpec, lower); |
| if (lower == newLower && upper == newUpper) { |
| newTypes[i] = oldPlaceHolders[i]; |
| } else { |
| ClassNode newPlaceHolder = ClassHelper.make(old.getName()); |
| GenericsType gt = new GenericsType(newPlaceHolder, newUpper, newLower); |
| gt.setPlaceholder(true); |
| newTypes[i] = gt; |
| } |
| } |
| } |
| return newTypes; |
| } |
| |
| private static final boolean PARAMETERIZED_TYPE_CACHE_ENABLED = Boolean.parseBoolean(getSystemPropertySafe("groovy.enable.parameterized.type.cache", "true")); |
| |
| private static final EvictableCache<ParameterizedTypeCacheKey, SoftReference<ClassNode>> PARAMETERIZED_TYPE_CACHE = new ConcurrentSoftCache<>(64); |
| |
| /** |
| * Clears the parameterized type cache. |
| * <p> |
| * It is useful to IDE as the type being compiled are continuously being edited/altered, see GROOVY-8675 |
| */ |
| public static void clearParameterizedTypeCache() { |
| PARAMETERIZED_TYPE_CACHE.clearAll(); |
| } |
| |
| /** |
| * Convenience method for {@link #findParameterizedTypeFromCache(ClassNode, ClassNode, boolean)} |
| * when the {@code tryToFindExactType} boolean is {@code false}. |
| */ |
| public static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType) { |
| return findParameterizedTypeFromCache(genericsClass, actualType, false); |
| } |
| |
| /** |
| * Try to get the parameterized type from the cache. |
| * <p> |
| * If no cached item found, cache and return the result of {@link #findParameterizedType(ClassNode,ClassNode,boolean)} |
| */ |
| public static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType, boolean tryToFindExactType) { |
| if (!PARAMETERIZED_TYPE_CACHE_ENABLED) { |
| return findParameterizedType(genericsClass, actualType, tryToFindExactType); |
| } |
| |
| SoftReference<ClassNode> sr = PARAMETERIZED_TYPE_CACHE.getAndPut( |
| new ParameterizedTypeCacheKey(genericsClass, actualType), |
| key -> new SoftReference<>(findParameterizedType(key.getGenericsClass(), key.getActualType(), tryToFindExactType))); |
| |
| return sr != null ? sr.get() : null; |
| } |
| |
| /** |
| * Convenience method for {@link #findParameterizedType(ClassNode,ClassNode,boolean)} when {@code tryToFindExactType} is {@code false}. |
| */ |
| public static ClassNode findParameterizedType(final ClassNode genericsClass, final ClassNode actualType) { |
| return findParameterizedType(genericsClass, actualType, false); |
| } |
| |
| /** |
| * Gets the parameterized type by searching the whole class hierarchy according to generics class and actual receiver. |
| * <p> |
| * {@link #findParameterizedTypeFromCache(ClassNode,ClassNode,boolean)} is strongly recommended for better performance. |
| */ |
| public static ClassNode findParameterizedType(final ClassNode genericsClass, final ClassNode actualType, final boolean tryToFindExactType) { |
| final GenericsType[] genericsTypes = genericsClass.getGenericsTypes(); |
| if (genericsTypes == null || genericsClass.isGenericsPlaceHolder()) { |
| return null; |
| } |
| |
| if (actualType.equals(genericsClass)) { |
| return actualType; |
| } |
| |
| LinkedList<ClassNode> todo = new LinkedList<>(); |
| Set<ClassNode> done = new HashSet<>(); |
| todo.add(actualType); |
| ClassNode type; |
| |
| while ((type = todo.poll()) != null) { |
| if (done.add(type)) { |
| if (!type.isInterface()) { |
| ClassNode cn = type.getUnresolvedSuperClass(); |
| if (cn != null && cn.redirect() != ClassHelper.OBJECT_TYPE) { |
| if (hasUnresolvedGenerics(cn)) { |
| cn = parameterizeType(type, cn); |
| } |
| if (cn.equals(genericsClass)) { |
| return cn; |
| } |
| todo.add(cn); |
| } |
| } |
| for (ClassNode cn : type.getInterfaces()) { |
| if (hasUnresolvedGenerics(cn)) { |
| cn = parameterizeType(type, cn); |
| } |
| if (cn.equals(genericsClass)) { |
| return cn; |
| } |
| todo.add(cn); |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * map declaring generics type to actual generics type, e.g. GROOVY-7204: |
| * declaring generics types: T, S extends Serializable |
| * actual generics types : String, Long |
| * |
| * the result map is [ |
| * T: String, |
| * S: Long |
| * ] |
| * |
| * The resolved types can not help us to choose methods correctly if the argument is a string: T: Object, S: Serializable |
| * so we need actual types: T: String, S: Long |
| */ |
| public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMap(final ClassNode declaringClass, final ClassNode actualReceiver) { |
| return correlateTypeParametersAndTypeArguments(declaringClass, actualReceiver, false); |
| } |
| |
| /** |
| * The method is similar with {@link GenericsUtils#makeDeclaringAndActualGenericsTypeMap(ClassNode, ClassNode)}, |
| * The main difference is that the method will try to map all placeholders found to the relevant exact types, |
| * but the other will not try even if the parameterized type has placeholders |
| * |
| * @param declaringClass the generics class node declaring the generics types |
| * @param actualReceiver the subclass class node |
| * @return the placeholder-to-actualtype mapping |
| * |
| * @since 3.0.0 |
| */ |
| public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMapOfExactType(final ClassNode declaringClass, final ClassNode actualReceiver) { |
| return correlateTypeParametersAndTypeArguments(declaringClass, actualReceiver, true); |
| } |
| |
| private static Map<GenericsType, GenericsType> correlateTypeParametersAndTypeArguments(final ClassNode declaringClass, final ClassNode actualReceiver, final boolean tryToFindExactType) { |
| ClassNode parameterizedType = findParameterizedTypeFromCache(declaringClass, actualReceiver, tryToFindExactType); |
| if (parameterizedType != null && parameterizedType.isRedirectNode() && !parameterizedType.isGenericsPlaceHolder()) { // GROOVY-10166 |
| // declaringClass may be "List<T> -> List<E>" and parameterizedType may be "List<String> -> List<E>" or "List<> -> List<E>" |
| final GenericsType[] typeParameters = parameterizedType.redirect().getGenericsTypes(); |
| if (typeParameters != null) { |
| final GenericsType[] typeArguments = parameterizedType.getGenericsTypes(); |
| final int m = typeArguments == null ? 0 : typeArguments.length; |
| final int n = typeParameters.length; |
| |
| Map<GenericsType, GenericsType> map = new LinkedHashMap<>(); |
| for (int i = 0; i < n; i += 1) { |
| map.put(typeParameters[i], i < m ? typeArguments[i] : erasure(typeParameters[i])); |
| } |
| return map; |
| } |
| } |
| return Collections.emptyMap(); |
| } |
| |
| /** |
| * @see org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport#extractType(GenericsType) |
| */ |
| private static GenericsType erasure(GenericsType gt) { |
| ClassNode cn = gt.getType().redirect(); // discard the placeholder |
| |
| if (gt.getType().getGenericsTypes() != null) |
| gt = gt.getType().getGenericsTypes()[0]; |
| |
| if (gt.getUpperBounds() != null) |
| cn = gt.getUpperBounds()[0]; // TODO: if length > 1 then union type? |
| |
| return cn.asGenericsType(); |
| } |
| |
| /** |
| * Checks if the type has any non-placeholder (aka resolved) generics. |
| * |
| * @since 3.0.0 |
| */ |
| public static boolean hasNonPlaceHolders(final ClassNode type) { |
| return checkPlaceHolders(type, gt -> !gt.isPlaceholder()); |
| } |
| |
| /** |
| * Checks if the type has any placeholder (aka unresolved) generics. |
| * |
| * @since 3.0.0 |
| */ |
| public static boolean hasPlaceHolders(final ClassNode type) { |
| return checkPlaceHolders(type, gt -> gt.isPlaceholder()); |
| } |
| |
| private static boolean checkPlaceHolders(final ClassNode type, final Predicate<GenericsType> p) { |
| if (type != null) { |
| GenericsType[] genericsTypes = type.getGenericsTypes(); |
| if (genericsTypes != null) { |
| for (GenericsType genericsType : genericsTypes) { |
| if (p.test(genericsType)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Checks for any placeholder (aka unresolved) generics. |
| */ |
| public static boolean hasUnresolvedGenerics(final ClassNode type) { |
| if (type.isGenericsPlaceHolder()) return true; |
| if (type.isArray()) { |
| return hasUnresolvedGenerics(type.getComponentType()); |
| } |
| GenericsType[] genericsTypes = type.getGenericsTypes(); |
| if (genericsTypes != null) { |
| for (GenericsType genericsType : genericsTypes) { |
| if (genericsType.isPlaceholder()) return true; |
| ClassNode lowerBound = genericsType.getLowerBound(); |
| ClassNode[] upperBounds = genericsType.getUpperBounds(); |
| if (lowerBound != null) { |
| if (hasUnresolvedGenerics(lowerBound)) return true; |
| } else if (upperBounds != null) { |
| for (ClassNode upperBound : upperBounds) { |
| if (hasUnresolvedGenerics(upperBound)) return true; |
| } |
| } else { |
| if (hasUnresolvedGenerics(genericsType.getType())) return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Gets the parameter and return types of the abstract method of SAM. |
| * |
| * If the abstract method is not parameterized, we will get generics placeholders, e.g. T, U |
| * For example, the abstract method of {@link java.util.function.Function} is |
| * <pre> |
| * R apply(T t); |
| * </pre> |
| * |
| * We parameterize the above interface as {@code Function<String, Integer>}, then the abstract method will be |
| * <pre> |
| * Integer apply(String t); |
| * </pre> |
| * |
| * When we call {@code parameterizeSAM} on the ClassNode {@code Function<String, Integer>}, |
| * we can get parameter types and return type of the above abstract method, |
| * i.e. ClassNode {@code ClassHelper.STRING_TYPE} and {@code ClassHelper.Integer_TYPE} |
| * |
| * @param samType the class node which contains only one abstract method |
| * |
| * @since 3.0.0 |
| */ |
| public static Tuple2<ClassNode[], ClassNode> parameterizeSAM(final ClassNode samType) { |
| MethodNode abstractMethod = ClassHelper.findSAM(samType); |
| |
| Map<GenericsType, GenericsType> generics = makeDeclaringAndActualGenericsTypeMapOfExactType(abstractMethod.getDeclaringClass(), samType); |
| Function<ClassNode , ClassNode> resolver = t -> { |
| return t.isGenericsPlaceHolder() ? findActualTypeByGenericsPlaceholderName(t.getUnresolvedName(), generics) : t; |
| }; |
| |
| ClassNode[] parameterTypes = Arrays.stream(abstractMethod.getParameters()).map(Parameter::getType).map(resolver).toArray(ClassNode[]::new); |
| ClassNode returnType = resolver.apply(abstractMethod.getReturnType()); |
| return new Tuple2<>(parameterTypes, returnType); |
| } |
| |
| /** |
| * Gets the actual type according to the placeholder name. |
| * |
| * @param placeholderName the placeholder name (i.e. "T", "E", etc.) |
| * @param genericsPlaceholderAndTypeMap the result of {@link #makeDeclaringAndActualGenericsTypeMap(ClassNode, ClassNode)} |
| */ |
| public static ClassNode findActualTypeByGenericsPlaceholderName(final String placeholderName, final Map<GenericsType, GenericsType> genericsPlaceholderAndTypeMap) { |
| Function<GenericsType, ClassNode> resolver = gt -> { |
| if (gt.isWildcard()) { |
| if (gt.getLowerBound() != null) { |
| return gt.getLowerBound(); |
| } |
| if (gt.getUpperBounds() != null) { |
| return gt.getUpperBounds()[0]; |
| } |
| } |
| return gt.getType(); |
| }; |
| |
| return genericsPlaceholderAndTypeMap.entrySet().stream() |
| .filter(e -> e.getKey().getName().equals(placeholderName)) |
| .map(Map.Entry::getValue).map(resolver).findFirst().orElse(null); |
| } |
| |
| private static class ParameterizedTypeCacheKey { |
| private ClassNode genericsClass; |
| private ClassNode actualType; |
| |
| public ParameterizedTypeCacheKey(ClassNode genericsClass, ClassNode actualType) { |
| this.genericsClass = genericsClass; |
| this.actualType = actualType; |
| } |
| |
| public ClassNode getGenericsClass() { |
| return genericsClass; |
| } |
| |
| @SuppressWarnings("unused") |
| public void setGenericsClass(ClassNode genericsClass) { |
| this.genericsClass = genericsClass; |
| } |
| |
| public ClassNode getActualType() { |
| return actualType; |
| } |
| |
| @SuppressWarnings("unused") |
| public void setActualType(ClassNode actualType) { |
| this.actualType = actualType; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| ParameterizedTypeCacheKey cacheKey = (ParameterizedTypeCacheKey) o; |
| |
| return genericsClass == cacheKey.genericsClass && |
| actualType == cacheKey.actualType; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(genericsClass, actualType); |
| } |
| } |
| } |